home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Celestin Apprentice 5
/
Apprentice-Release5.iso
/
Source Code
/
Libraries
/
DCLAP 6d
/
dclap6d
/
network
/
nsclilib
/
ni_lib.c
< prev
next >
Wrap
Text File
|
1996-07-05
|
117KB
|
3,470 lines
/*
* ===========================================================================
*
* PUBLIC DOMAIN NOTICE
* National Center for Biotechnology Information
*
* This software/database is a "United States Government Work" under the
* terms of the United States Copyright Act. It was written as part of
* the author's official duties as a United States Government employee and
* thus cannot be copyrighted. This software/database is freely available
* to the public for use. The National Library of Medicine and the U.S.
* Government have not placed any restriction on its use or reproduction.
*
* Although all reasonable efforts have been taken to ensure the accuracy
* and reliability of the software and data, the NLM and the U.S.
* Government do not and cannot warrant the performance or results that
* may be obtained by using this software or data. The NLM and the U.S.
* Government disclaim all warranties, express or implied, including
* warranties of performance, merchantability or fitness for any particular
* purpose.
*
* Please cite the author in any work or product based on this material.
*
* ===========================================================================
*
* File Name: ni_lib.c
*
* Author: Beatty, Gish, Epstein
*
* Version Creation Date: 1/1/92
*
* $Revision: 4.0 $
*
* File Description:
* This file is a library of functions to be used by server application
* and client software, using the NCBI "network services" paradigm.
*
*
* Modifications:
* --------------------------------------------------------------------------
* Date Name Description of modification
* ------- ---------- -----------------------------------------------------
* 4/27/92 Epstein Added extensive in-line commentary, and removed all tabs.
* 5/11/92 Epstein Removed unused function NI_SVCRequestGet(); added support
* for the connection ID to be written to a CONID file each
* time that the value of conid is updated; in practice,
* only dispatcher will update a CONID file.
* 6/22/92 Epstein For UNIX signals, catch the SIGPIPE error which can
* occur when writing to a socket which is no longer
* connected.
* 7/06/92 Epstein Changed sokselectw() to examine the SO_ERROR socket option
* after select()-ing a socket to which we were attempting a
* connection. This eliminates "false connects", i.e.,
* unsuccessful connection attempts which look successful
* because the select() call returns 1.
* 7/14/92 Epstein Changed NI_SetDispatcher() and NI_InitServices() to use
* a configurable timeout parameter, and in the process
* also changed sokselectw() to have a timeout parameter,
* 1/21/93 Epstein Add dispatcher-list support, and add dispatcher-list
* parameter to NI_InitServices().
* 2/12/93 Epstein Use new boolean parameter to MsgMakeHandle(), indicating
* whether or not it should create a socket. This was
* an attempted fix for a Mac problem ... it later
* turned out to be an incorrect problem-fix, but also
* does no harm.
* 2/24/93 Epstein Fix long-standing Mac bug, by correctly destroying
* services handle and hence closing an open socket.
* 3/02/93 Epstein Add functions to write dispatcher-configuration info
* to a config file. This provides a standardized
* mechanisms which applications may use for net services
* configuration. Also added platform functions, so
* that dispatcher/server complex can know what type
* of platform a client is running on, assuming that the
* client is telling the truth.
* 3/03/93 Epstein Cleanup variable initialization.
* 3/08/93 Epstein Improve error messages & cleanup to NI_InitServices,
* include reason in login failure message, and add
* client platform to service request.
* 3/09/93 Epstein Add HaltServices() function to simplify cleanup.
* 3/22/93 Epstein Fix typecast for getsockopt(), and, more importantly,
* remember to return the computed value in NI_GetPlatform.
* 3/23/93 Epstein Support VMS/TGV, and add NETP_INET_ prefixes to
* conditional-compilation symbols.
* 3/24/93 Epstein Clear the caller's pointer in NI_SetDispConfig().
* 3/31/93 Epstein Add dispatcher pointer as context for all network
* services operations; this allows an application
* to use more than one dispatcher at a time, at the
* expense of slightly greater complexity. Also add
* a "Generic Init" function, which can be used by
* an application to obtain network-services in a
* simplified, standardized manner.
* 3/31/93 Epstein Move debug and module variables to their correct home.
* 4/02/93 Epstein Add WinSock support.
* 4/12/93 Schuler Add MAKEWORD macro.
* 4/21/93 Schuler Removed function prototypes for NI_AsnRead, NI_AsnWrite
* 5/07/93 Epstein Move WSAStartup() code to a better place, add workaround
* for connection attempt on a non-blocking socket in PC-NFS
* 4.0, add more platform definitions.
* 5/24/93 Epstein Add separate error codes for TCP/IP initialization
* failure and inability to resolve local host name.
* 5/25/93 Epstein Add configuration-file workaround for PC-NFS 5.0 bug,
* where NIS sometimes fails on the PC's own host name.
* 5/27/93 Epstein Incorporate pragmatic "Gestalt" code for Vibrant
* scrolling workaround for WinSock under Windows 3.1,
* add add SOCK_INDEX_ERRNO macro to workaround another
* WinSock pecularity.
* 6/02/93 Schuler Change "Handle" to "MonitorPtr" for Monitors.
* 6/07/93 Epstein Added generic timer functions.
* Also add missing revision history, derived from
* RCS file.
* 6/09/93 Epstein Added activity hook to report network activity back
* to an application.
* 6/14/93 Epstein Changed "Generic" logic to cause UNIX/VMS loginname
* to override loginname, rather than vice versa. Also
* setup DispatchConnect() logic to set client's declared
* IP address to 0.0.0.0, rather than causing an error,
* in the case where the client cannot resolve its own
* host name. In this case, the dispatcher will set its
* own opinion of the client address based upon
* getpeername().
* 6/15/93 Epstein Eliminate "Gestalt" code for Vibrant scrolling
* workaround for WinSock under Windows 3.1, since the
* solution for this problem does not require its use.
* 6/25/93 Epstein Fix activity-hook action for service disconnection (had
* erroneously announced dispatcher-disconnection), and
* add logic to try to avoid getservbyname() by looking
* up dispatcher port # and (loport,hiport) in NCBI
* configuration file instead of in NIS. As a last resort,
* look up the name in NIS if the entry in the NCBI
* config. file is non-numeric. Also, change the client
* port lookup mechanism for Macintoshes to add a configured
* "delta" value to the low port number. This results in
* allowing several Network Services applications to run
* concurrently on a Mac without port conflicts.
* 7/08/93 Epstein Fix list traversal error in NI_ProcessTimers()
* 7/08/93 Epstein Added a counter as a failsafe mechanism in
* NI_ProcessTimers(), since previous fix attempt failed.
* 7/09/93 Epstein Changed a few #define names to avoid Alpha compilation
* warnings, and added reference count to dispatcher data
* structure.
* 8/09/93 Epstein Improve diagnostics when a listen() call fails
* 8/23/93 Epstein Add currentDisp variable so that the currently-attached
* dispatcher is used when the parameter to NI_SetDispatcher
* is NULL.
* 8/31/93 Epstein Fix host vs. network order when comparing port numbers.
* 9/08/93 Epstein Added new stackDescription variable, to be able to
* report to the dispatcher the identity of the vendor
* of the WinSock stack
* 9/09/93 Epstein Fix use of currentDisp variable to correctly compare
* new dispatcher request again current dispatcher.
*11/24/93 Epstein Added code to support standalone servers and clients
* which communicate with standalone servers or "service
* brokers", which listen on a specific port (to be
* augmented later).
*11/30/93 Epstein Made standalone server code UNIX-only, to avoid
* possible compilation errors on other platforms. However,
* it should be possible in principle to run and test a
* standalone server on a non-UNIX host. Also, added
* limited security to standalone servers.
*12/08/93 Epstein Fixed service connection activity hook, per discussion
* with Kyle Hart.
*01/19/94 Schuler Post error (SEV_INFO) on WinSock initialization
* failure showing WinSock's error code.
*01/28/94 Schuler Replace "NETP_INET_MACTCP" with "NETP_INET_MACTCP"
*01/28/94 Schuler Defined THIS_MODULE and THIS_FILE
*02/14/94 Epstein Add preliminary RSAREF encryption support
*02/22/94 Epstein Add DISP_RECONN_ACTION logic to allow users to breakout
* or quit if unable to contact primary dispatcher.
*02/24/94 Epstein Make use of new NI_DupPubKey function, and insert
* newlines in macros to make editing easier.
*03/03/94 Epstein Reduce memory leaks, suppress non-printing characters
* in winsock.dll.
*04/22/94 Epstein Change error handling to use SEV_ERROR and SEV_WARNING
* (ErrPostEx). Also do a better job of detecting
* inability to connect to dispatcher in DispatchConnect(),
* because under Solaris getsockopt() doesn't correctly
* detect an error.
*04/25/94 Epstein Cosmetic change for error when NACK received from
* Dispatcher.
*05/04/94 Epstein Add logic to allow a mixture of encrypted and
* unencrypted services, determined by ENCRYPTION=FALSE
* fields in the appropriate sections in the config.
* files.
*06/08/94 Epstein Add SOCKS support (probably not correct yet) by
* asking Dispatcher to provide a SVC_PRE_RESPONSE message
* which contains the server's IP address.
*06/10/94 Epstein More SOCKS refinement, plus added tracing for SOCKS
*06/15/94 Epstein Produce working SOCKS version, by changing the protocol
* so that a SOCKSified client uses two service request
* messages; one to learn the IP address of the server
* to which it will be assigned, and the second "real"
* service request after it has bound the 'listen' port
* on the SOCKS daemon.
*07/01/94 Epstein Determine at runtime whether or not to use SOCKS,
* methodology, based upon presence or absence of
* SOCKS_CONF file.
*07/07/94 Epstein Updated commentary.
*09/22/94 Epstein Improved standalone server code by using SO_REUSEADDR
* option, making it possible for identical servers to
* run consecutively without waiting for sockets to
* shutdown.
*12/02/94 Epstein Changed NI_GenericGetService() to have environment
* variables override configuration file for SERVICE_NAME
* and RESOURCE_TYPE. This is mostly just a convenience
* for internal NCBI use, allowing the use of a single
* config. file while using scripts to force the use
* of a different service.
*12/06/94 Epstein Added connectDelay, adminInfo and motd fields.
* The client reports the time which it took to
* establish a connection in their connectDelay field.
* The Dispatcher provides the name of the Network
* administrator and a secondary message-of-the-day in
* the other two fields.
*12/21/94 Epstein Added instrumentation for socket management
*01/11/95 Epstein Change socket instrumentation to suppress some
* errors for WinSock 1.1.
*01/13/95 Epstein Add new GetLAPType() function to report Mac clients'
* TCP/IP implementation ("Ethernet", "PPP", etc.) to
* the Dispatcher.
*03/20/95 Epstein Fix InitServices/EndServices logic to correctly only
* establish a single Dispatcher connection for multiple
* services.
*03/29/95 Epstein Reduce calls to Message() in favor of ErrPostEx().
*06/02/95 Epstein Fix bindPort() to address host/network byte ordering;
* this should correct a byte-ordering problem on little-
* endian hosts which use SOCKS.
*06/06/95 Epstein For UNIX, try to get the user's name from the USER
* environment variable before inquiring getlogin();
* this can save CPU time and contention on some systems,
* since access to utmp can be slow.
*06/12/95 Epstein Another byte-ordering fix for SOCKS for the SOCKS
* proxy's port number.
* ==========================================================================
*
*
* RCS Modification History:
* $Log: ni_lib.c,v $
* Revision 4.0 1995/07/26 13:56:32 ostell
* force revision to 4.0
*
* Revision 1.74 1995/07/12 14:53:28 epstein
* Another byte-ordering fix for SOCKS
*
* Revision 1.73 1995/06/06 10:04:52 epstein
* use USER environment variable in lieu of getlogin()
*
* Revision 1.72 95/06/02 16:58:43 epstein
* fix bindPort() using htonl() to correct byte-ordering problem on little-endian clients which use SOCKS
*
* Revision 1.71 95/05/17 17:52:18 epstein
* add RCS log revision history
*
*/
#define _NCBINET_LOCAL_VARS
#define THIS_MODULE g_nsclient_module
#define THIS_FILE _this_file
#define __NI_LIB__
#include "ncbinet.h"
#include "ni_lib.h"
#include "ni_msg.h"
char * g_nsclient_module = "nsclient";
static char *_this_file = __FILE__;
#define ERR_KEY_MISMATCH "The public encryption key received from the dispatcher does\n\
not match what is on file. There is a slight security risk\n\
that this key is being presented by a \"spoofer\" rather than\n\
the real dispatcher. You may wish to contact the NCBI by\n\
other means to determine whether this new key is valid. Do\n\
you wish to accept this as the new key and continue?"
#define ERR_KEY_NOPREVKEY "A public encryption key was just received from the dispatcher,\n\
but no key is currently on file. There is a slight security\n\
risk that this key is being presented by a \"spoofer\" rather\n\
than the real dispatcher. You may wish to contact the NCBI by\n\
other means to determine whether this new key is valid. Do\n\
you wish to accept this as the new key and continue?"
#ifdef NETP_INET_NEWT
#define SIN_ADDR sin_addr.S_un.S_addr
#define H_ADDR_TYPE Uint4Ptr
#else
#define SIN_ADDR sin_addr
#define H_ADDR_TYPE struct in_addr *
#endif
#ifdef WIN16
#ifndef MAKEWORD
#define MAKEWORD(a,b) ((WORD)(((BYTE)(a)) | ((WORD)((BYTE)(b))) <<8))
#endif
#endif
typedef struct NI_Timer {
time_t timeout;
NI_TimeoutHook hook;
Pointer hookParam;
} NI_Timer, PNTR NI_TimerPtr;
/* GLOBALS */
static FILE *conid_fp = NULL; /* File pointer for CONID */
static NodePtr timerHead = NULL; /* list of timers */
static NI_NetServHook activityHook = NULL;
static NI_DispatcherPtr currentDisp = NULL;
static CharPtr stackDescription = NULL;
static fd_set openfds;
#ifdef NETP_INET_WSOCK
static Int4 wsaStartupCount = 0;
#endif
NILoginPtr NI_MakeMsgLogin PROTO((void));
static Int2 SetIdentity PROTO((NI_DispatcherPtr disp, CharPtr user, CharPtr group, CharPtr domain));
static void HaltServices PROTO((NI_DispatcherPtr disp));
static NI_HandPtr DispatchConnect PROTO((NI_DispatcherPtr disp, CharPtr host, CharPtr name, int timeout));
static Uint2 bindPort PROTO((int sok, struct sockaddr_in PNTR sokadr, Int2 loport, Int2 hiport, Uint4 remoteHost));
static Int2 CopyIdentity PROTO((NI_DispatcherPtr disp, NI_UidPtr uid));
/** ( these are prototyped in ni_msg.h [GDS] )
Int2 NI_AsnRead PROTO((Pointer fd, CharPtr buf, Uint2 len));
Int2 NI_AsnWrite PROTO((Pointer fd, CharPtr buf, Uint2 len));
**/
int sokselectr PROTO((int fd));
int sokselectw PROTO((int fd, int timeout));
int getAsnError PROTO((char * str));
void SetConFilePtr PROTO((FILE *fp));
void CloseConFile PROTO((void));
/*
* Purpose: Specify which dispatcher a client should try to connect to
*
* Parameters:
* disp Usually NULL, the pointer to a pre-existing Dispatcher
* structure
* host Name of the host (Fully Qualified Domain Name) to use
* svc Name of the "service" to try to use on that host
* dispserialnum Serial number of dispatcher-list. Use -1 if no response
* list is desired, or 0 if the serial number is not known.
*
*
* Description:
* Set up the dispatcher name which should be used, and the
* name of the service on that dispatcher. If other parameters
* have been specified previously, free the memory associated
* with those names.
*
* Note:
* There are useful defaults for "svc". When in doubt, call
* this function with a second arguement of NULL.
*/
NI_DispatcherPtr
NI_SetDispatcher(NI_DispatcherPtr disp, CharPtr host, CharPtr svc, int timeout,
Int4 dispserialnum, ValNodePtr encryption)
{
if (disp == NULL) {
if (currentDisp != NULL)
{ /* use current dispatcher if it matches what the caller wants */
if (StringCmp(svc, currentDisp->dispServiceName) == 0 &&
StringCmp(host, currentDisp->dispHostName) == 0) {
return currentDisp;
}
}
disp = (NI_DispatcherPtr) MemNew(sizeof(NI_Dispatcher));
if (disp == NULL)
return NULL;
disp->reqResponse = NULL;
disp->dispHostName = NULL;
disp->dispServiceName = NULL;
disp->dispSerialNo = 0;
disp->localHostAddr[0] = '\0';
disp->dispHP = NULL;
disp->svcsHP = NULL;
disp->clientPort = 0;
disp->identity = NULL;
disp->dispTimeout = 0;
disp->referenceCount = 0;
disp->brokeredDummy = FALSE;
disp->encryptInfo = encryption;
disp->useSocks = FALSE;
#ifdef SOCKS_CONF
{
FILE *fp;
if ((fp = FileOpen(SOCKS_CONF, "r")) != NULL)
{
disp->useSocks = TRUE;
FileClose(fp);
}
}
#endif /* SOCKS_CONF */
}
if (disp->dispHostName != NULL) {
MemFree(disp->dispHostName);
disp->dispHostName = NULL;
}
if (disp->dispServiceName != NULL) {
MemFree(disp->dispServiceName);
disp->dispServiceName = NULL;
}
if (host != NULL)
disp->dispHostName = StringSave(host);
if (svc != NULL)
disp->dispServiceName = StringSave(svc);
disp->dispSerialNo = dispserialnum;
disp->dispTimeout = timeout;
return disp;
} /* NI_SetDispatcher */
/*
* Purpose: Try to establish a connection to the dispatcher
*
* Parameters:
* disp A pointer to the dispatcher structure
* user User name to try on the dispatcher
* group Group name to try on the dispatcher
* password Password for this user name
* dip A pointer to the caller's list of dispatchers; this should
* be used by the caller to update its information
* regarding which dispatchers to try in the future
* (if dip == NULL, then no retries will be made to get
* alternate dispatchers)
*
* Returns:
* -1, if something failed (ni_errno indicates the nature of
* the problem)
* 0, if everything was successful
* 1, if we are connected to the dispatcher which we requested,
* but the list of current dispatchers has changed
* 2, if we are connected to a dispatcher, but not the one
* which we requested
*
*
* Description:
* Perform any WinSock and/or SOCKS initialization as necessary
* Connect to the dispatcher
* Set-up a socket for an incoming connection from a server
* application process (non-SOCKS clients only)
* Send a LOGIN message to the dispatcher
* Wait for an ACK or NACK response from the dispatcher (or for
* a timeout to occur)
* If the response was a NACK due to the dispatcher being a
* backup dispatcher, then try the dispatcher which it
* directs us to
*/
Int2
NI_InitServices(NI_DispatcherPtr disp, CharPtr user, CharPtr group, CharPtr password, NI_DispInfoPtr PNTR dip)
{
NIMsgPtr mp, imp;
NILoginPtr loginp;
struct sockaddr_in svcsAddr;
struct timeval timeout;
int ready;
NIDispInfoPtr dispinfo = NULL;
Boolean newDispToTry;
Int2 altDispTries = 0;
Int2 retval = 0;
int status;
fd_set readfds;
NI_PubKeyPtr pubKey = NULL;
Boolean failed;
#ifdef NETP_INET_WSOCK
WSADATA wsaData;
#endif /* NETP_INET_WSOCK */
#if defined(OS_MAC) && defined(NETP_INET_MACTCP)
extern char * GetLAPType(void);
char *lapType = GetLAPType();
#endif /* OS_MAC && NETP_INET_MACTCP */
if (disp == NULL)
{
ni_errno = NIE_MISC;
return -1;
}
if (disp->referenceCount > 0 && disp->dispHP != NULL)
{ /* already connected */
disp->referenceCount++;
return 0;
}
#ifdef NETP_INET_WSOCK
status = WSAStartup(MAKEWORD(1,1),&wsaData);
if (status != 0)
ErrPostEx(SEV_ERROR,0,0,"WinSock 1.1 initialization failure, code %d", status-WSABASEERR);
/* Try WinSock 1.1 and 1.0 in that order of preference */
if (status != 0 && (status = WSAStartup(MAKEWORD(1,0),&wsaData)) != 0)
{
ErrPostEx(SEV_ERROR,0,0,"WinSock 1.0 initialization failure, code %d", status-WSABASEERR);
ni_errno = NIE_TCPINITFAIL;
return -1;
}
TRACE("%s\n", wsaData.szDescription);
if (stackDescription != NULL)
{
MemFree(stackDescription);
}
stackDescription = StringSave(wsaData.szDescription);
for (status = StrLen(stackDescription) - 1; status >= 0; status--)
{
/* convert characters which are incompatible with VisibleString */
if (stackDescription[status] < ' ' || stackDescription[status] > '~')
stackDescription[status] = '#';
}
wsaStartupCount++;
#endif
#if defined(OS_MAC) && defined(NETP_INET_MACTCP)
if (lapType != NULL)
{
stackDescription = StringSave(lapType);
}
#endif /* OS_MAC && NETP_INET_MACTCP */
#ifdef NETP_SOCKS
if (disp->useSocks)
{
char path[128];
Nlm_ProgramPath(path, sizeof path);
SOCKSinit(path);
TRACE("Performed SOCKSinit(%s)\n", path);
}
#endif /* NETP_SOCKS */
if (disp->dispHostName == NULL)
disp->dispHostName = StringSave(NI_DEFAULT_HOST);
if (disp->dispServiceName == NULL)
disp->dispServiceName = StringSave(NI_DEFAULT_SERVICE);
do {
newDispToTry = FALSE;
disp->svcsHP = NULL;
if ((disp->dispHP = DispatchConnect(disp, disp->dispHostName, disp->dispServiceName, disp->dispTimeout))
== NULL) {
NI_DestroyDispInfo(dispinfo);
HaltServices (disp);
ErrPostEx(SEV_WARNING,0,0, "NI_InitServices: Unable to connect to host <%s>, error <%s>", disp->dispHostName, ni_errlist[ni_errno]);
return -1; /* ni_errno remains set */
}
if ((disp->svcsHP = MsgMakeHandle(TRUE)) == NULL) {
NI_DestroyDispInfo(dispinfo);
HaltServices (disp);
ErrPostEx (SEV_ERROR, CTX_NCBICORE, CORE_UNKNOWN, "NI_InitServices: Unable to allocate resources to communicate with %s", disp->dispHostName);
return -1;
}
if (disp->dispTimeout > 0)
{
MsgSetReadTimeout(disp->svcsHP, disp->dispTimeout);
}
if (! disp->useSocks)
{ /* must defer binding and listening for SOCKS connection */
if ((disp->clientPort = bindPort(disp->svcsHP->sok, &svcsAddr, disp->loport, disp->hiport, 0)) == 0) {
MsgDestroyHandle(disp->svcsHP);
disp->svcsHP = NULL;
ni_errno = NIE_NOBIND; /* can't bind a free application socket */
NI_DestroyDispInfo(dispinfo);
HaltServices (disp);
ErrPostEx (SEV_ERROR, CTX_NCBICORE, CORE_UNKNOWN, "NI_InitServices: <%s>", ni_errlist[ni_errno]);
return -1;
}
if ((status = NI_LISTEN(disp->svcsHP->sok, 5)) < 0) {
#ifdef NETP_INET_NEWT
SOCK_ERRNO = ABS(status);
#endif
StringCpy(ni_errtext, sys_errlist[SOCK_INDEX_ERRNO]);
ni_errno = NIE_NOLISTEN;
NI_DestroyDispInfo(dispinfo);
HaltServices (disp);
ErrPostEx (SEV_ERROR, CTX_NCBICORE, CORE_UNKNOWN, "NI_InitServices: <%s> <port %d, errno %d>", ni_errlist[ni_errno], (int) disp->clientPort, (int) SOCK_ERRNO);
return -1;
}
}
SetIdentity(disp, user, group, NI_DEFAULT_DOMAIN);
loginp = NI_MakeMsgLogin();
NI_DestroyUid(loginp->uid);
loginp->uid = NI_MakeUid();
loginp->seqno = disp->dispHP->seqno++;
loginp->dispserialno = disp->dispSerialNo;
loginp->connectDelay = disp->dispHP->connectDelay;
if (disp->encryptInfo != NULL && NI_EncrAvailable())
{
loginp->encryptionDesired = TRUE;
if (disp->encryptInfo->data.ptrvalue != NULL)
{
pubKey = (NI_PubKeyPtr) disp->encryptInfo->data.ptrvalue;
loginp->pubKey = (NIPubKeyPtr) NI_PubKeyDup(pubKey);
}
}
CopyIdentity(disp, loginp->uid);
if (password != NULL)
loginp->password = StringSave(password);
mp = MsgBuild(NI_LOGIN, disp->dispHP->conid, (VoidPtr) loginp);
if (MsgWrite(disp->dispHP, mp, FALSE) < 0) {
if (getAsnError(ni_errtext) == ECONNRESET)
ni_errno = NIE_MAXCONNS;
else
ni_errno = NIE_MSGWRITE;
MsgDestroyHandle(disp->svcsHP);
disp->svcsHP = NULL;
NI_DestroyDispInfo(dispinfo);
HaltServices (disp);
ErrPostEx (SEV_ERROR, CTX_NCBICORE, CORE_UNKNOWN, "NI_InitServices: <%s>", ni_errlist[ni_errno]);
return -1;
}
/* blocks until ACK or ERROR from dispatcher or TIMEOUT */
timeout.tv_sec = (Uint4) NI_TIMEOUT_SECS;
timeout.tv_usec = 0;
FD_ZERO(&readfds);
FD_SET(disp->dispHP->sok, &readfds);
while ((ready = NI_select(FD_SETSIZE, &readfds, NULL, NULL, &timeout)) < 0) {
if (SOCK_ERRNO == EINTR)
; /* repeat while interrupted */
else {
MsgDestroyHandle(disp->svcsHP);
disp->svcsHP = NULL;
ni_errno = NIE_SELECT; /* select error */
NI_DestroyDispInfo(dispinfo);
ErrPostEx (SEV_ERROR, CTX_NCBICORE, CORE_UNKNOWN, "NI_InitServices: <%s>", ni_errlist[ni_errno]);
return -1;
}
}
if (FD_ISSET(disp->dispHP->sok, &readfds) != 0) {
if ((imp = MsgRead(disp->dispHP, FALSE)) == NULL) {
if (getAsnError(ni_errtext) == ECONNRESET)
ni_errno = NIE_MAXCONNS;
else
ni_errno = NIE_MSGREAD;
MsgDestroyHandle(disp->svcsHP);
disp->svcsHP = NULL;
NI_DestroyDispInfo(dispinfo);
HaltServices (disp);
ErrPostEx (SEV_ERROR, CTX_NCBICORE, CORE_UNKNOWN, "NI_InitServices: <%s>", ni_errlist[ni_errno]);
return -1;
}
switch (imp->type) {
case NI_ACK:
/************************************************************/
/* even though we connected successfully to the dispatcher, */
/* it may have given us more up-to-date information on the */
/* latest list of dispatchers which should be tried; if so, */
/* pass the updated list back to the caller */
/************************************************************/
if (imp->msun.ack->dispinfo != NULL) {
if (dispinfo != NULL)
{
NI_DestroyDispInfo(dispinfo);
dispinfo = NULL;
}
dispinfo = imp->msun.ack->dispinfo;
imp->msun.ack->dispinfo = NULL; /* for clean free */
}
if (disp->encryptInfo != NULL && NI_EncrAvailable())
{
if (dispinfo != NULL && dispinfo->pubKey != NULL)
{
failed = FALSE;
if (pubKey == NULL)
{
failed = Message(MSG_YN, ERR_KEY_NOPREVKEY) ==
ANS_NO;
ni_errno = NIE_NEWKEYNOTACCPT;
} else {
if (! NI_PubKeysEqual(pubKey, (NI_PubKeyPtr) dispinfo->pubKey))
{
failed = Message(MSG_YN, ERR_KEY_MISMATCH) ==
ANS_NO;
ni_errno = NIE_NEWKEYMISMATCH;
}
}
if (failed)
{
HaltServices(disp);
MsgDestroy(imp);
return -1;
} else {
/* replace the key */
NI_DestroyPubKey((NIPubKeyPtr) pubKey);
pubKey = (NI_PubKeyPtr) dispinfo->pubKey;
dispinfo->pubKey = NULL;
disp->encryptInfo->data.ptrvalue = (Pointer) pubKey;
}
}
}
if (dispinfo != NULL && dip != NULL) {
if (*dip != NULL)
NI_DestroyDispInfo((NIDispInfoPtr) *dip);
*dip = (NI_DispInfoPtr) dispinfo;
dispinfo = NULL;
retval = 1;
}
else {
NI_DestroyDispInfo(dispinfo);
}
if (imp->msun.ack->motd != NULL &&
imp->msun.ack->motd[0] != NULLB)
{
disp->motd = imp->msun.ack->motd;
imp->msun.ack->motd = NULL; /* for clean free */
}
if (imp->msun.ack->adminInfo != NULL &&
imp->msun.ack->adminInfo[0] != NULLB)
{
disp->adminInfo = imp->msun.ack->adminInfo;
imp->msun.ack->adminInfo = NULL; /* for clean free */
}
#ifdef OS_UNIX
signal(SIGPIPE, SIG_IGN); /* catch socket errors */
#endif /* OS_UNIX */
MsgDestroy(imp);
disp->referenceCount++;
if (currentDisp == NULL)
{
currentDisp = disp;
}
return retval; /* only good return */
case NI_NACK:
ni_errno = (enum ni_error) imp->msun.nack->code;
if (imp->msun.nack->reason != NULL)
{
StringCpy(ni_errtext, imp->msun.nack->reason);
} else {
ni_errtext[0] = '\0';
}
if (dispinfo != NULL)
{
NI_DestroyDispInfo(dispinfo);
dispinfo = NULL;
}
dispinfo = imp->msun.nack->dispinfo;
imp->msun.nack->dispinfo = NULL; /* for clean free */
if (ni_errno == NIE_BACKUPDISP && dispinfo != NULL &&
dispinfo->numdispatchers > 0 && dip != NULL &&
++altDispTries < MAX_ALT_DISP_TRIES)
{
MsgDestroy(imp);
HaltServices (disp);
NI_SetDispatcher(disp, dispinfo->displist[0], disp->dispServiceName,
disp->dispTimeout, dispinfo->serialno, disp->encryptInfo);
newDispToTry = TRUE;
retval = 2;
break;
}
MsgDestroy(imp);
MsgDestroyHandle(disp->svcsHP);
disp->svcsHP = NULL;
NI_DestroyDispInfo(dispinfo);
HaltServices (disp);
ErrPostEx (SEV_ERROR, CTX_NCBICORE, CORE_UNKNOWN, "NI_InitServices: <%s>\n%s", ni_errlist[ni_errno], ni_errtext);
return -1;
default:
MsgDestroy(imp);
ni_errno = NIE_MSGUNK;
MsgDestroyHandle(disp->svcsHP);
disp->svcsHP = NULL;
NI_DestroyDispInfo(dispinfo);
HaltServices (disp);
ErrPostEx (SEV_ERROR, CTX_NCBICORE, CORE_UNKNOWN, "NI_InitServices: <%s>", ni_errlist[ni_errno]);
return -1;
}
}
} while (newDispToTry);
MsgDestroyHandle(disp->svcsHP);
disp->svcsHP = NULL;
ni_errno = NIE_LOGTIMEOUT; /* TIMEOUT */
NI_DestroyDispInfo(dispinfo);
HaltServices (disp);
ErrPostEx (SEV_ERROR, CTX_NCBICORE, CORE_UNKNOWN, "NI_InitServices: <%s>", ni_errlist[ni_errno]);
return -1;
} /* NI_InitServices */
/*
* Purpose: Init network services based on information in config file
*
* Parameters:
* configFile Name of NCBI-style configuration file. If NULL, defaults
* to "NCBI"
* configSection Section with NCBI-style configuration file. If NULL,
* defaults to "NET_SERV"
* showMonitor Boolean; if TRUE, display a monitor while re-trying
* for an alternate dispatcher
* lastDispatcher Pointer to where this function should store the name
* of the dispatcher which was actually used (may be NULL
* if the caller does not care about this value)
* lastDispLen Maximum length of lastDispatcher
*
* Returns:
* NULL, if unable to contact dispatcher
* a pointer to the Dispatcher structure, otherwise
*
*
* Description:
* Extracts a dispatcher name and a user name from a configuration
* file. If necessary, tries other dispatchers, in order, as
* listed in configuration file. Also sets up encryption, if
* the client is encryption-capable and encryption is requested
* in the configuration file.
*
*
* Note:
* This function is provided as a convenience to developers who
* wish to use Network Services. Use of this function is not
* integral to the use of Network Services ... it is merely a
* convenience.
*/
NI_DispatcherPtr
NI_GenericInit (CharPtr configFile, CharPtr configSection, Boolean showMonitor, CharPtr lastDispatcher, Int2 lastDispLen)
{
char *def_user;
char username[64];
char groupname[20];
char password[20];
char dispname[60];
char disp_config[10];
char disp_msg[110];
char buf[60];
Boolean more_disps;
int alternate = 1;
int disp_timeout;
Int4 disp_serialno;
Nlm_Monitor *mon = NULL;
NI_DispInfoPtr dip = NULL;
NI_DispatcherPtr disp = NULL;
Boolean someBrokered;
ValNodePtr encryptInfo = NULL;
NI_PubKeyPtr keyCopy = NULL;
Boolean doEncr = FALSE;
Boolean quitOnDispConnFailure = FALSE;
Boolean showMessage = FALSE;
#ifdef OS_UNIX
char *getlogin PROTO((void));
#endif
/******************* open the network connnection *********/
#define NI_DISP_NAME "dispatch1.nlm.nih.gov"
#define NI_USER_NAME "anonymous"
#define NI_GROUP_NAME "GUEST"
def_user = NI_USER_NAME;
if (configFile == NULL)
configFile = "NCBI";
if (configSection == NULL)
configSection = "NET_SERV";
Nlm_GetAppParam(configFile, configSection, "DISP_USERNAME", NI_USER_NAME, username,
sizeof username);
/* the user's login name overrides the config file */
/* for UNIX or VMS systems (or, for the future, any system where the */
/* user name can be determined), use the user's login name as the default */
def_user = NULL;
#ifdef OS_UNIX
if ((def_user = getenv("USER")) == NULL)
{
def_user = getlogin();
}
#endif
#ifdef OS_VMS
def_user = getenv("USER");
#endif
if (def_user != NULL)
{
StrNCpy(username, def_user, sizeof username);
}
Nlm_GetAppParam(configFile, configSection, "DISP_GROUPNAME", NI_GROUP_NAME, groupname,
sizeof groupname);
Nlm_GetAppParam(configFile, configSection, "DISP_PASSWORD", "", password,
sizeof password); /* default = NONE */
Nlm_GetAppParam(configFile, configSection, "DISP_TIMEOUT", "0", buf, sizeof buf);
disp_timeout = atoi(buf);
Nlm_GetAppParam(configFile, configSection, "DISPSERIALNO", "0", buf, sizeof buf);
disp_serialno = atoi(buf);
Nlm_GetAppParam(configFile, configSection, "DISPATCHER", NI_DISP_NAME, dispname,
sizeof dispname);
Nlm_GetAppParam(configFile, configSection, "SOME_BROKERED", "FALSE", buf, sizeof buf);
someBrokered = StrICmp(buf, "TRUE") == 0;
Nlm_GetAppParam(configFile, configSection, "DISP_RECONN_ACTION", "CONT", buf, sizeof buf);
showMessage = StrICmp(buf, "ASK") == 0;
quitOnDispConnFailure = StrICmp(buf, "QUIT") == 0;
Nlm_GetAppParam(configFile, configSection, "ENCRYPTION_DESIRED", "FALSE", buf, sizeof buf);
if (StrICmp(buf, "TRUE") == 0 && NI_EncrAvailable())
{
doEncr = TRUE;
encryptInfo = ValNodeNew(NULL);
encryptInfo->data.ptrvalue = (Pointer) NI_ReadPubKeyFromConfig();
keyCopy = NI_PubKeyDup((NI_PubKeyPtr) encryptInfo->data.ptrvalue);
}
do {
if (alternate == 2 && showMonitor)
{
mon = MonitorStrNew("Unable to contact primary dispatcher", 35);
}
if (alternate >= 2)
{
if (showMessage)
{
sprintf (disp_msg, "Unable to contact primary dispatcher. Ready to try\ndispatcher #%d <", alternate);
StrCat(disp_msg, dispname);
StrCat(disp_msg, ">. Continue?");
if (Message(MSG_YN, disp_msg) == ANS_NO)
break;
} else {
sprintf(disp_msg, "Trying dispatcher #%d <", alternate);
StrCat(disp_msg, dispname);
StrCat(disp_msg, ">");
if (showMonitor) {
MonitorStrValue(mon, disp_msg);
}
}
}
if (lastDispatcher != NULL) {
StrNCpy(lastDispatcher, dispname, lastDispLen);
}
disp = NI_SetDispatcher (NULL, dispname, NULL, disp_timeout, disp_serialno, encryptInfo);
if (someBrokered)
{
disp->brokeredDummy = TRUE;
disp->someBrokered = TRUE;
disp->referenceCount++;
ValNodeFree (encryptInfo);
return disp;
}
if (NI_InitServices(disp, username, groupname[0] == '\0' ? NULL : groupname,
password[0] == '\0' ? NULL : password, &dip) >= 0)
{
if (dip != NULL && dip->serialno != disp_serialno) {
NI_SetDispConfig (&dip, dispname, sizeof dispname);
}
if (mon != NULL)
MonitorFree(mon);
if (disp->encryptInfo != NULL &&
disp->encryptInfo->data.ptrvalue != NULL &&
! NI_PubKeysEqual(keyCopy,
(NI_PubKeyPtr) disp->encryptInfo->data.ptrvalue))
{
NI_WritePubKeyToConfig ((NI_PubKeyPtr) disp->encryptInfo->data.ptrvalue);
}
NI_DestroyPubKey ((NIPubKeyPtr) keyCopy);
return disp;
}
ErrShow ();
NI_EndServices (disp);
sprintf(disp_config, "DISP_ALT_%d", alternate++);
more_disps = Nlm_GetAppParam(configFile, configSection, disp_config, "", dispname,
sizeof dispname);
if (doEncr)
{
encryptInfo = ValNodeNew(NULL);
encryptInfo->data.ptrvalue = (Pointer)
NI_PubKeyDup((NI_PubKeyPtr) keyCopy);
}
} while (more_disps && ! quitOnDispConnFailure);
if (mon != NULL)
MonitorFree(mon);
ValNodeFree (encryptInfo);
NI_DestroyPubKey ((NIPubKeyPtr) keyCopy);
ErrPostEx(SEV_ERROR,0,0, "NI_InitServices: Unable to connect to any dispatcher");
return NULL;
}
/*
* Purpose: Get a network service based on information in config file
*
* Parameters:
* disp Pointer to the dispatcher structure obtained from a
* previous call to NI_SetDispatcher or NI_GenericInit
* configFile Name of NCBI-style configuration file. If NULL, defaults
* to "NCBI"
* defService The default service/resource/resource-type name, if
* not specified otherwise in configuration file.
* hasResource Boolean; if TRUE, ask for a resource when requesting
* service
*
* Returns:
* NULL, if unable to obtain service
* a pointer to the service-structure, otherwise
*
*
* Description:
* Extracts a service name and other service data from a
* configuration file, and attempts to obtain that service.
* As a special case, handle communication with a "brokered
* server" (a server which is already listening on a port, where
* no communication needs to be performed with the dispatcher).
* Also disable data encryption for this service request, if
* explicitly specified in the configuration file.
*
*
* Note:
* This function is provided as a convenience to developers who
* wish to use Network Services. Use of this function is not
* integral to the use of Network Services ... it is merely a
* convenience.
*
* For UNIX systems, environment variables can be used to
* override the config. file's values for SERVICE_NAME and
* RESOURCE_TYPE.
*/
NI_HandPtr
NI_GenericGetService (NI_DispatcherPtr disp, CharPtr configFile, CharPtr configSection, CharPtr defService, Boolean hasResource)
{
char buf[40];
char service[40];
char resource[40];
char res_type[40];
int serv_min;
int serv_max;
int res_min;
int res_max;
char brokeredIpaddr[40];
Uint2 port;
NI_HandPtr result;
ValNodePtr savEncrypt;
#ifdef OS_UNIX
CharPtr envName = MemNew(StrLen(configSection) + 20);
CharPtr envValue;
#endif
if (configFile == NULL)
configFile = "NCBI";
Nlm_GetAppParam(configFile, configSection, "SERVICE_NAME", defService,
service, sizeof service);
#ifdef OS_UNIX
/* environment variable overrides config. file */
sprintf (envName, "NI_SERVICE_NAME_%s", configSection);
if ((envValue = getenv(envName)) != NULL)
{
StrCpy (service, envValue);
}
#endif /* OS_UNIX */
Nlm_GetAppParam(configFile, configSection, "SERV_VERS_MIN", "1",
buf, sizeof buf);
serv_min = atoi(buf);
Nlm_GetAppParam(configFile, configSection, "SERV_VERS_MAX", "0",
buf, sizeof buf);
serv_max = atoi(buf);
res_min = 1;
res_max = 0;
if (hasResource) {
Nlm_GetAppParam(configFile, configSection, "RESOURCE_NAME", defService,
resource, sizeof resource);
Nlm_GetAppParam(configFile, configSection, "RESOURCE_TYPE", defService,
res_type, sizeof res_type);
#ifdef OS_UNIX
/* environment variable overrides config. file */
sprintf (envName, "NI_RESOURCE_TYPE_%s", configSection);
if ((envValue = getenv(envName)) != NULL)
{
StrCpy (res_type, envValue);
}
#endif /* OS_UNIX */
Nlm_GetAppParam(configFile, configSection, "RES_VERS_MIN", "1",
buf, sizeof buf);
res_min = atoi(buf);
Nlm_GetAppParam(configFile, configSection, "RES_VERS_MAX", "0",
buf, sizeof buf);
res_max = atoi(buf);
}
#ifdef OS_UNIX
MemFree (envName);
#endif /* OS_UNIX */
if (disp->someBrokered)
{
Nlm_GetAppParam(configFile, configSection, "BROKERED_PORT", "0",
buf, sizeof buf);
port = atoi(buf);
Nlm_GetAppParam(configFile, configSection, "BROKERED_IPADDR", "",
brokeredIpaddr, sizeof brokeredIpaddr);
if (port != 0 && brokeredIpaddr[0] != '\0')
{ /* simulate service request by connecting to that port */
struct sockaddr_in serv_addr;
NI_HandPtr sHP;
int timeout = 30;
int status;
MemFill((VoidPtr) &serv_addr, '\0', sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = inet_addr(brokeredIpaddr);
serv_addr.sin_port = port;
if ((sHP = MsgMakeHandle(TRUE)) == NULL)
return NULL;
MsgSetLJError(sHP);
sHP->hostname = StringSave(brokeredIpaddr);
if (activityHook != NULL)
{
activityHook(sHP, NetServHook_svcreq, 0);
}
RETRY:
#ifndef NETP_INET_NEWT
if ((status = NI_CONNECT(sHP->sok, (struct sockaddr PNTR) &serv_addr, sizeof(serv_addr))) < 0) { /* } */
#else
if ((status = NI_CONNECT(sHP->sok, &serv_addr, sizeof(serv_addr))) < 0) {
SOCK_ERRNO = ABS(status);
#endif
switch (SOCK_ERRNO) {
case EINTR:
goto RETRY;
#ifdef NETP_INET_PCNFS
/* This is apparently a bug in PC-NFS 4.0 ... a connection attempt */
/* on a non-blocking socket yields errno == 0 */
case 0:
#endif /* NETP_INET_PCNFS */
case EWOULDBLOCK:
case EINPROGRESS:
/* if the connect()ion is not established immediately, a */
/* select() can be performed where the corresponding "write" */
/* file descriptor will be enabled once the connect()ion has been*/
/* established */
if (sokselectw(sHP->sok, timeout) == 0) {
return sHP;
}
break;
default:
break;
}
MsgDestroyHandle(sHP);
ni_errno = NIE_BROKSVCCONN; /* can't connect to brokered service */
return NULL;
}
{
Char buf[16];
Char key[8];
Int2 len;
if ((len = Nlm_GetAppParam("NCBI", "NET_SERV", "DESKEY", "", buf, sizeof buf)) > 0 &&
AsnTypeStringToHex(buf, len, key))
{
NI_SetupDESEncryption(sHP, (UcharPtr) key);
}
}
return sHP;
} else {
if (disp->brokeredDummy)
{ /* JAE ... establish a true dispatcher connection here */
}
}
}
savEncrypt = disp->encryptInfo;
if (Nlm_GetAppParam(configFile, configSection, "ENCRYPTION", "TRUE",
buf, sizeof buf) > 0 && StrICmp(buf, "FALSE") == 0)
{
/* temporarily disable encryption */
disp->encryptInfo = NULL;
}
result = NI_ServiceGet(disp, service, serv_min, serv_max,
hasResource ? resource : NULL, res_type, res_min,
res_max);
disp->encryptInfo = savEncrypt;
return result;
}
/*
* Purpose: Write dispatcher-configuration information to a config file
*
* Parameters:
* dipp A pointer to the caller's list of dispatchers, obtained
* from NI_InitServices()
* dispatcher The caller's dispatcher string
* dispLen Length of the caller's dispatcher string
*
* Returns:
* 0, if bad parameters were provided
* the dispatcher-list serial number, otherwise
*
*
* Description:
* Sets up the "NCBI" configuration file with the following
* entries in the "NET_SERV" section:
* * DISPATCHER is the primary dispatcher name
* * DISP_ALT_n for every alternate dispatcher, 1 <= n, a smaller
* n indicates a higher priority alternate dispatcher
* * DISPSERIALNO is the serial number of the dispatcher list
* obtained from a remote dispatcher. This serial number should
* be unique for all time ... the dispatcher's serial number
* must be changed whenever the master list is modified.
*
* Note:
* This configuration mechanism is only _one_ recommended
* mechanism for network services dispatcher configuration. The
* application may perform this configuration in any manner
* deemed appropriate by the application programmer.
*
* The value returned by this function is the recommended value
* for the dispserialno parameter in a subsequent call to
* NI_InitDispatcher().
*/
Int4
NI_SetDispConfig(NI_DispInfoPtr PNTR dipp, CharPtr dispatcher, Int2 dispLen)
{
Int2 num;
Char dispConfig[20];
char buf[10];
Int4 retval;
NI_DispInfoPtr dip;
if (dipp == NULL || (dip = *dipp) == NULL)
{
if (dispatcher != NULL)
{
dispatcher[0] = '\0';
}
return 0;
}
if (dip->numdispatchers > 0 && dip->displist != NULL)
{
StringNCpy (dispatcher, dip->displist[0], dispLen);
SetAppParam ("NCBI", "NET_SERV", "DISPATCHER", dip->displist[0]);
}
for (num = 1; num < dip->numdispatchers; num++)
{
sprintf (dispConfig, "DISP_ALT_%d", num);
SetAppParam ("NCBI", "NET_SERV", dispConfig, dip->displist[num]);
}
/* wipe out any extraneous old configuration */
for (num = dip->numdispatchers; num < 100; num++)
{
sprintf (dispConfig, "DISP_ALT_%d", num);
if (Nlm_GetAppParam("NCBI", "NET_SERV", dispConfig, "", buf, sizeof buf) <= 0)
{
break;
}
SetAppParam ("NCBI", "NET_SERV", dispConfig, NULL);
}
retval = dip->serialno;
sprintf (buf, "%ld", (long) dip->serialno);
SetAppParam ("NCBI", "NET_SERV", "DISPSERIALNO", buf);
NI_DestroyDispInfo ((NIDispInfoPtr) dip);
*dipp = NULL;
return retval;
}
/*
* Purpose: End use of network services
*
* Parameters:
* disp A pointer to the dispatcher structure
*
* Returns:
* 0 (always)
*
*
* Description:
* Tear down the sockets and data structures associated with
* the dispatcher and a server, and free all memory associated
* with data structures.
*/
Int2
NI_EndServices(NI_DispatcherPtr disp)
{
Int2 openSockets;
if (disp == NULL)
return 0;
if (disp->referenceCount > 0)
disp->referenceCount--;
if (disp->referenceCount <= 0)
{
if (disp == currentDisp)
{
currentDisp = NULL;
}
HaltServices (disp);
NI_SetDispatcher(disp, NULL, NULL, 0, 0, NULL); /* free mem */
if (stackDescription != NULL)
{
MemFree(stackDescription);
stackDescription = NULL;
}
MemFree(disp);
/* For historical reasons pertaining to Network Entrez, a single open
socket does not constitute an error in this context. However, at
program exit time, a single open socket does constitute a serious
problem. */
if ((openSockets = NI_SocketsOpen()) > 1)
{
ErrPostEx(SEV_WARNING,0,0, "At end-services time, %d sockets are still open", openSockets);
}
}
return 0;
} /* NI_EndServices */
/*
* Purpose: Request a catalog from the dispatcher
*
* Parameters:
* disp A pointer to the dispatcher structure
*
* Returns:
* NULL, if unable to obtain the catalog
* a pointer to the received catalog data structure, otherwise
*
*
* Description:
* Send a request to the dispatcher, requesting a catalog, and
* wait (up to some timeout) for a response. The dispatcher's
* response should either be that catalog, or a NACK.
*/
NICatalogPtr
NI_GetCatalog(NI_DispatcherPtr disp)
{
NICatalogPtr catp;
NIMsgPtr mp, imp;
NICmdPtr cmdp;
struct timeval timeout;
int ready;
fd_set readfds;
if (disp == NULL)
return NULL;
cmdp = (NICmdPtr) NI_MakeMsgCmd();
cmdp->seqno = disp->dispHP->seqno++;
cmdp->code = NI_SEND_CATALOG;
if ((mp = MsgBuild(NI_COMMAND, disp->dispHP->conid, (VoidPtr) cmdp)) == NULL) {
ni_errno = NIE_MISC; /* unable to alloc mem for Msg */
return NULL;
}
if (MsgWrite(disp->dispHP, mp, FALSE) < 0) {
ni_errno = NIE_MSGWRITE;
return NULL;
}
/* blocks until response from dispatcher or TIMEOUT */
timeout.tv_sec = (Uint4) NI_TIMEOUT_SECS;
timeout.tv_usec = 0;
FD_ZERO(&readfds);
FD_SET(disp->dispHP->sok, &readfds);
while ((ready = NI_select(FD_SETSIZE, &readfds, NULL, NULL, &timeout)) < 0) {
if (SOCK_ERRNO == EINTR)
; /* repeat while interrupted */
else {
ni_errno = NIE_SELECT; /* select error */
return NULL;
}
}
if (FD_ISSET(disp->dispHP->sok, &readfds) != 0) {
if ((imp = MsgRead(disp->dispHP, FALSE)) == NULL) {
LOG_SOCKET(disp->dispHP->sok, FALSE);
NI_CLOSESOCKET(disp->dispHP->sok);
ni_errno = NIE_MSGREAD;
return NULL;
}
switch (imp->type) {
case NI_CATALOG:
catp = imp->msun.catalog;
imp->msun.catalog = NULL;
ni_errno = NIE_NO_ERROR;
MsgDestroy(imp);
return catp;
break;
case NI_NACK:
ni_errno = (enum ni_error) imp->msun.nack->code;
if (imp->msun.nack->reason != NULL)
StringCpy(ni_errtext, imp->msun.nack->reason);
else
ni_errtext[0] = '\0';
MsgDestroy(imp);
return NULL;
default:
MsgDestroy(imp);
ni_errno = NIE_MSGUNK; /* Unknown MSG type */
return NULL;
}
}
ni_errno = NIE_CMDTIMEOUT; /* TIMEOUT */
return NULL;
} /* NI_GetCatalog */
/*
* Purpose: Create the data structure for a service request
*
* Parameters:
* disp A pointer to the dispatcher structure
*
* Returns:
* a pointer to the newly created data structure
*
*
* Description:
* Allocate the memory for a service request data structure,
* and fill in some of the fields.
* Note:
* There are two ways for a program to issue a service request:
* (1) Multi-step, general method (like IRS form 1040)
* * Build a request with NI_SVCRequestBuild()
* * Populate the request with a specific service request using
* NI_RequestSetService()
* * Populate the request with zero or more resource requests
* calling NI_RequestAddResource() once for every resource
* * Send the request with NI_ServiceRequest(), and (hopefully)
* obtain a connection to a service provider
* * At some later time, delete the request (to save memory)
* (2) One-stop shopping, for simple requirement (like form 1040EZ)
* * Do everything for a service and up to one resource using
* NI_ServiceGet()
*/
NI_ReqPtr
NI_SVCRequestBuild(NI_DispatcherPtr disp)
{
NI_ReqPtr reqp;
if (disp == NULL)
return NULL;
reqp = (NI_ReqPtr) NI_MakeRequest();
reqp->clientPort = (Uint2) disp->clientPort;
if (disp->useSocks)
{
/* tell the Dispatcher that it should use getpeername() to determine */
/* the IP address of the SOCKS daemon */
reqp->clientAddr = StringSave("0.0.0.0");
} else {
reqp->clientAddr = StringSave(disp->localHostAddr);
}
reqp->dispatcher = disp; /* should not be freed when destroying Req */
return reqp;
} /* NI_SVCRequestBuild */
/*
* Purpose: Destroy a service request data structure
*
* Parameters:
* reqp A pointer to the data structure to be destroyed
*
*
* Description:
* Free all the resources associated with a service request
*/
void
NI_SVCRequestDestroy(NI_ReqPtr reqp)
{
NI_DestroyRequest(reqp);
} /* NI_SVCRequestDestroy */
/*
* Purpose: Make a service request for a service and up to one resource
*
* Parameters:
* disp A pointer to the dispatcher structure
* svc Name of requested service
* svcvermin Minimum version number requested for this service
* svcvermax Maximum version number requested for this service
* res Name of requested resource (possibly NULL)
* resvermin Minimum version number requested for this resource
* resvermax Maximum version number requested for this resource
*
* Returns:
* The result of the service request
*
*
* Description:
* Create and issue a service request for the specified
* parameters.
*/
NI_HandPtr
NI_ServiceGet(NI_DispatcherPtr disp, CharPtr svc, Uint2 svcvermin, Uint2 svcvermax, CharPtr res, CharPtr restype, Uint2 resvermin, Uint2 resvermax)
{
NI_ReqPtr reqp;
if (disp == NULL)
return NULL;
reqp = NI_SVCRequestBuild(disp);
NI_RequestSetService(reqp, svc, svcvermin, svcvermax);
if (res != NULL)
NI_RequestAddResource(reqp, res, restype, resvermin, resvermax);
return NI_ServiceRequest(reqp);
} /* NI_ServiceGet */
/*
* Purpose: Issue the specified service request
*
* Parameters:
* req The pre-formatted service request
*
* Returns:
* A message handle to the server which is servicing our request,
* if successful
* NULL, otherwise (ni_errno will indicate a more precise cause)
*
*
* Description:
* Create and issue a service request for the specified
* service request, as follows:
* * Create a data structure to which the resulting service
* connection can be attached
* * Send the service request to the dispatcher
* * Wait for the following two events, in either order:
* (1) A response from the dispatcher, which is either a
* SVC_RESPONSE (good), or a NACK (bad) {or a timeout}
* (2) A connection request from the server, which we then
* accept()
* * If both of the two events occur successfully, return with
* success, else, return with failure.
*
* Note:
* If the caller's Dispatcher data structure indicates that
* encryption should be performed, then a DES key is
* pseudorandomly generated prior to issuing the service request,
* and is encrypted using public-key encryption. Following
* successful establishment of the client<->server session, the
* DES key is used to encrypt the ensuing session using
* cypher-block-chaining.
*
* For SOCKSified clients, a different protocol is used where
* the client first sends a "pre-service-request", asking the
* IP address of the computer to which the Dispatcher will assign
* the request. Upon receipt of a NI_SVC_PRE_RESPONSE message
* containing that IP address, the client performs a SOCKSified
* bind()ing indicating that the specified IP address will
* "call back". Having determine the port number which has
* been bound on the SOCKS proxy, the client sends the "real"
* service request containing the SOCKS proxy's port number
* and a reminder as to which IP address the Dispatcher
* "promised" would be assigned. After that, processing
* proceeds normally, with the server connecting-back (via
* the SOCKS proxy) and the Dispatcher sending a SVC_RESPONSE
* acknowledgement.
*
* Note that SOCKS and encryption are completely orthogonal
* with respect to each other, and a client may use either, both,
* or neither.
*/
NI_HandPtr
NI_ServiceRequest(NI_ReqPtr req)
{
NI_HandPtr sconnhp;
#ifdef NETP_INET_MACTCP
Int4 sconnlen;
#else
int sconnlen;
#endif
struct sockaddr_in sconnaddr;
NIMsgPtr mp, imp;
NISvcReqPtr svcreqp;
struct timeval timeout;
int ready;
Boolean disp_contact = FALSE, serv_contact = FALSE;
Uint4 this_req;
fd_set readfds;
NI_DispatcherPtr disp = req->dispatcher;
Uchar desKey[8];
#ifdef NETP_SOCKS
struct sockaddr_in svcsAddr;
Int2 status;
#endif
ni_errtext[0] = '\0';
if ((sconnhp = MsgMakeHandle(FALSE)) == NULL) {
ni_errno = NIE_MAKEHAND;
return NULL;
}
svcreqp = NI_MakeMsgSvcreq();
svcreqp->seqno = disp->dispHP->seqno++;
svcreqp->platform = (Uint4) NI_GetPlatform();
if (stackDescription != NULL)
{
svcreqp->applId = StringSave(stackDescription);
}
if (disp->encryptInfo != NULL && NI_EncrAvailable())
{
UcharPtr encryptedDesKey;
Int2 encryptedLen;
NI_GenerateDESKey (desKey);
encryptedLen = NI_PubKeyEncrypt((NI_PubKeyPtr) disp->encryptInfo->data.ptrvalue,
desKey, sizeof desKey, &encryptedDesKey);
if (encryptedLen <= 0)
{
NI_DestroyRequest(req);
MsgDestroyHandle(sconnhp);
ni_errno = NIE_PUBKEYENCRFAIL;
return NULL;
}
/* convert the DES key into a ByteStore */
svcreqp->desKey = BSNew(encryptedLen);
BSWrite (svcreqp->desKey, (VoidPtr) encryptedDesKey, encryptedLen);
MemFree (encryptedDesKey);
}
this_req = svcreqp->seqno;
CopyIdentity(disp, svcreqp->uid);
NI_DestroyRequest(svcreqp->request);
svcreqp->request = req;
if (disp->useSocks)
{
svcreqp->wantPreResponse = TRUE;
}
if ((mp = MsgBuild(NI_SVC_REQUEST, disp->dispHP->conid, (VoidPtr) svcreqp)) == NULL) {
NI_DestroyRequest(req);
MsgDestroyHandle(sconnhp);
ni_errno = NIE_MISC; /* unable to alloc mem for Msg */
return NULL;
}
if (MsgWrite(disp->dispHP, mp, svcreqp->wantPreResponse) < 0) {
MsgDestroyHandle(sconnhp);
ni_errno = NIE_MSGWRITE;
return NULL;
}
/* blocks until SVC_RESPONSE from dispatcher and service or NACK or TIMEOUT */
while (!disp_contact || !serv_contact) {
timeout.tv_sec = (Uint4) NI_TIMEOUT_SECS;
timeout.tv_usec = 0;
FD_ZERO(&readfds);
FD_SET(disp->dispHP->sok, &readfds);
FD_SET(disp->svcsHP->sok, &readfds);
while ((ready = NI_select(FD_SETSIZE, &readfds, NULL, NULL, &timeout)) < 0) {
if (SOCK_ERRNO == EINTR)
; /* repeat while interrupted */
else {
MsgDestroyHandle(sconnhp);
ni_errno = NIE_SELECT; /* select error */
return NULL;
}
}
if (ready == 0) {
MsgDestroyHandle(sconnhp);
ni_errno = NIE_DSPTIMEOUT; /* TIMEOUT */
return NULL;
}
if (FD_ISSET(disp->dispHP->sok, &readfds) != 0) {
if ((imp = MsgRead(disp->dispHP, FALSE)) == NULL) {
LOG_SOCKET(disp->dispHP->sok, FALSE);
NI_CLOSESOCKET(disp->dispHP->sok);
MsgDestroyHandle(sconnhp);
ni_errno = NIE_MSGREAD;
return NULL;
}
disp_contact = TRUE;
switch (imp->type) {
case NI_SVC_PRE_RESPONSE:
#ifdef NETP_SOCKS
if (disp->useSocks)
{
/* must defer binding and listening for SOCKS connection */
/* until server's IP address is known */
TRACE("Processing SOCKS SVC_PRE_RESPONSE\n");
/* SOCKS can't deal well with non-blocking connections */
NI_SETBLOCKING(disp->svcsHP->sok);
if ((disp->clientPort = bindPort(disp->svcsHP->sok, &svcsAddr, disp->loport, disp->hiport, imp->msun.preresp->server_ip)) == 0) {
TRACE("bindPort failed\n");
MsgDestroyHandle(sconnhp);
disp->svcsHP = NULL;
ni_errno = NIE_NOBIND; /* can't bind a free application socket */
ErrPost (CTX_NCBICORE, CORE_UNKNOWN, "NI_ServiceRequest: <%s>", ni_errlist[ni_errno]);
return NULL;
}
TRACE("bindPort succeeded, port = %d\n", disp->clientPort);
if (NI_GETSOCKNAME(disp->svcsHP->sok, &svcsAddr, &sconnlen) >= 0)
disp->clientPort = ntohs(svcsAddr.sin_port);
req->clientPort = disp->clientPort;
svcreqp->server_ip = imp->msun.preresp->server_ip;
svcreqp->wantPreResponse = FALSE;
if (MsgWrite(disp->dispHP, mp, FALSE) < 0) {
MsgDestroyHandle(sconnhp);
ni_errno = NIE_MSGWRITE;
return NULL;
}
disp_contact = FALSE; /* now waiting to hear one more msg */
TRACE("After GETSOCKNAME, port = %d\n", disp->clientPort);
if ((status = NI_LISTEN(disp->svcsHP->sok, 5)) < 0) {
#ifdef NETP_INET_NEWT
SOCK_ERRNO = ABS(status);
#endif
TRACE("Listen failed, errno = %d\n", SOCK_ERRNO);
MsgDestroyHandle(sconnhp);
StringCpy(ni_errtext, sys_errlist[SOCK_INDEX_ERRNO]);
ni_errno = NIE_NOLISTEN;
ErrPost (CTX_NCBICORE, CORE_UNKNOWN, "NI_ServiceRequest: <%s> <port %d, errno %d>", ni_errlist[ni_errno], (int) disp->clientPort, (int) SOCK_ERRNO);
return NULL;
}
}
#endif
/* non-SOCKS clients ignore this message, and should never receive it */
TRACE("Listen succeeded\n");
MsgDestroy(imp);
break;
case NI_SVC_RESPONSE:
if (disp->useSocks)
{
TRACE("Got SOCKS service response from Dispatcher\n");
}
ni_errno = NIE_NO_ERROR;
NI_DestroyRequest(disp->reqResponse);
disp->reqResponse = imp->msun.svcresp->request;
sconnhp->hostname = StringSave(disp->reqResponse->clientAddr);
imp->msun.svcresp->request = NULL;
MsgDestroy(imp);
break;
case NI_NACK:
if (disp->useSocks)
{
TRACE("Got SOCKS NACK from Dispatcher\n");
}
ni_errno = (enum ni_error) imp->msun.nack->code;
if (imp->msun.nack->reason != NULL)
StringCpy(ni_errtext, imp->msun.nack->reason);
else
ni_errtext[0] = '\0';
MsgDestroy(imp);
MsgDestroyHandle(sconnhp);
return NULL;
default:
if (disp->useSocks)
{
TRACE("Got SOCKS unknown message type from Dispatcher\n");
}
MsgDestroy(imp);
MsgDestroyHandle(sconnhp);
ni_errno = NIE_MSGUNK; /* Unknown MSG type */
sprintf(ni_errtext, "%d", imp->type);
return NULL;
}
}
if ((FD_ISSET(disp->svcsHP->sok, &readfds) != 0) && !serv_contact) {
sconnlen = sizeof(sconnaddr);
#ifdef NETP_INET_NEWT
sconnhp->sok = NI_ACCEPT(disp->svcsHP->sok, &sconnaddr, &sconnlen);
#else
sconnhp->sok = NI_ACCEPT(disp->svcsHP->sok, (struct sockaddr PNTR) &sconnaddr, &sconnlen);
#endif /* NETP_INET_NEWT */
LOG_SOCKET(sconnhp->sok, TRUE);
#ifdef NETP_SOCKS
if (disp->useSocks)
{
TRACE("Got connection from server, socket %d\n", sconnhp->sok);
MsgDestroyHandle(disp->svcsHP);
disp->svcsHP = MsgMakeHandle(TRUE); /* for next time */
}
#endif /* NETP_SOCKS */
if (sconnhp->sok < 0) {
#ifdef NETP_INET_NEWT
SOCK_ERRNO = ABS(sconnhp->sok);
#endif
MsgDestroyHandle(sconnhp);
StringCpy(ni_errtext, sys_errlist[SOCK_INDEX_ERRNO]);
ni_errno = NIE_NOACCEPT; /* application accept error */
return NULL;
}
serv_contact = TRUE;
}
}
if (activityHook != NULL)
{
activityHook(sconnhp, NetServHook_svcreq, 0);
}
if (disp->encryptInfo != NULL && NI_EncrAvailable())
{
NI_SetupDESEncryption(sconnhp, desKey);
}
return sconnhp;
} /* NI_ServiceRequest */
/*
* Purpose: Disconnect from a service provider
*
* Parameters:
* mhp Message handle for the server
*
* Returns:
* 0, always
*
*
* Description:
* Disconnect from a service provider, essentially by just
* closing the communication socket to that service provider.
*/
Int2
NI_ServiceDisconnect(NI_HandPtr mhp)
{
if (activityHook != NULL)
{
activityHook(mhp, NetServHook_svcdisconn, 0);
}
MsgDestroyHandle(mhp);
return 0;
} /* NI_ServiceDisconnect */
/*
* Purpose: Obtain the read file descriptor from a "message handle"
*
* Parameters:
* handp Message handle
*
* Returns:
* Socket associated with message handle
*
*
* Description:
* Get the read file desciptor from a message handle. This
* might be useful, for example, when wishing to perform
* "direct" I/O to the socket after a connection has been
* established with a server/client.
*/
int
NI_ServiceGetReadFd(NI_HandPtr handp)
{
return handp->sok;
} /* NI_ServiceGetReadFd */
/*
* Purpose: Obtain the write file descriptor from a "message handle"
*
* Parameters:
* handp Message handle
*
* Returns:
* Socket associated with message handle
*
*
* Description:
* Get the write file desciptor from a message handle. This
* might be useful, for example, when wishing to perform
* "direct" I/O to the socket after a connection has been
* established with a server/client.
*/
int
NI_ServiceGetWriteFd(NI_HandPtr handp)
{
return handp->sok;
} /* NI_ServiceGetWriteFd */
/*
* Purpose: Populate a service request with a service name and version #s
*
* Parameters:
* req Service request
* name Service name
* vermin Minimum version number for this service
* vermax Maximum version number for this service
*
* Returns:
* -1, if the name is a NULL pointer
* 0, otherwise
*
*
* Description:
* Populate the service request with the specified service name
* and version numbers, dynamically allocating space for the
* service name.
*/
Int2
NI_RequestSetService(NI_ReqPtr req, CharPtr name, Uint2 vermin, Uint2 vermax)
{
if (name == NULL) {
ni_errno = NIE_INVAL;
return -1;
}
req->service->name = StringSave(name);
req->service->minVersion = vermin;
req->service->maxVersion = vermax;
req->service->typeL = NULL;
return 0;
} /* NI_RequestSetService */
/*
* Purpose: Populate a service request with an additional resource
*
* Parameters:
* req Service request
* name Resource name
* type Service type
* vermin Minimum version number for this resource
* vermax Maximum version number for this resource
*
* Returns:
* -1, if the name is a NULL pointer
* 0, otherwise
*
*
* Description:
* Insert the information for this resource into a list of
* resources associated with this service request. This
* function may be called one or more times (or, not at all) to
* populate a service request with one or more resources.
*/
Int2
NI_RequestAddResource(NI_ReqPtr req, CharPtr name, CharPtr type, Uint2 vermin, Uint2 vermax)
{
NIResPtr resp;
if (name == NULL) {
ni_errno = NIE_INVAL;
return -1;
}
resp = NI_MakeResource();
resp->name = StringSave(name);
if (type != NULL)
resp->type = StringSave(type);
resp->minVersion = vermin;
resp->maxVersion = vermax;
req->resourceL = ListInsertPrev((VoidPtr) resp, req->resourceL); /* add to end of list */
return 0;
} /* NI_RequestAddResource */
/* THESE FUNCTIONS NOT VISIBLE TO API USER */
/*
* Purpose: Partially halt Network Services
*
* Parameters:
* disp A pointer to the dispatcher structure
*
* Description:
* Halt network services, except refrain from freeing the
* parameters which are set by NI_SetDispatcher().
*/
static void
HaltServices (NI_DispatcherPtr disp)
{
if (disp == NULL)
return;
if (disp->referenceCount > 0)
return;
if (activityHook != NULL)
{
activityHook((NI_HandPtr) disp, NetServHook_dispdisconn, 0);
}
MsgDestroyHandle(disp->dispHP);
MsgDestroyHandle(disp->svcsHP);
NI_DestroyRequest(disp->reqResponse);
if (disp->identity != NULL) {
MemFree (disp->identity->username);
MemFree (disp->identity->group);
MemFree (disp->identity->domain);
MemFree (disp->identity);
disp->identity = NULL;
}
disp->dispHP = NULL;
disp->svcsHP = NULL;
disp->reqResponse = NULL;
if (disp->encryptInfo != NULL)
{
if (disp->encryptInfo != NULL)
NI_DestroyPubKey((NIPubKeyPtr) disp->encryptInfo->data.ptrvalue);
ValNodeFree(disp->encryptInfo);
}
#ifdef NETP_INET_WSOCK
/* we have an obligation to perform one cleanup call for every Startup */
while (wsaStartupCount-- > 0)
{
WSACleanup();
}
#endif
}
/*
* Purpose: Lookup a port # in config file and possible NIS
*
* Parameters:
* service Name of config. file entry
* networkOrder Boolean, indicates whether value should be returned in host
* order or network order.
*
* Description:
* Look up the specified entry in the NCBI config. file, and
* lookup in NIS the name obtained from the config file if it's
* non-numeric.
*
* Note:
* The intent of this function is that, in most cases, the
* GetAppParam() entry will not be present, and a default value
* will be used instead. The getservbyname() call is intended
* to be a last resort, because this may be slow on some systems.
*/
static Uint2
GetByConfigOrServ(CharPtr service, Boolean networkOrder)
{
struct servent PNTR portEntry;
Char buf[50];
Uint2 port;
if (Nlm_GetAppParam("NCBI", "NET_SERV", service, "", buf, sizeof buf) <= 0)
{
port = 0;
} else {
if (StrSpn(buf, "0123456789") == StrLen(buf))
{ /* all numeric */
port = atoi(buf);
if (networkOrder)
port = htons(port);
} else {
/* entry from configuration file is name to use in getservbyname */
if ((portEntry = getservbyname(buf, "tcp")) == NULL)
{
port = 0;
} else {
port = portEntry->s_port;
if (! networkOrder)
port = ntohs(port);
}
}
}
return port;
}
/*
* Purpose: Connect to the dispatcher
*
* Parameters:
* disp A pointer to the dispatcher structure
* host Name of the host on which dispatcher resides
* service Name of the "service" (i.e., port) to which we should connect
* timeout How long to wait for dispatcher to respond, 0 ==> use default
*
* Returns:
* NULL, if the attempt to connect failed
* a pointer to the "Msg" structure for the dispatcher, otherwise
*
*
* Description:
* Connect to the dispatcher on the specified hostname on the
* specified service (where a service maps to a port number).
* This is done by establishing a socket to the dispatcher,
* and then connect()ing to that socket; the dispatcher should
* be listen()ing on that socket, and should subsequently accept()
* the connection request.
*
* While doing this, also obtain other useful information;
* namely, the dotted IP address of the local host, and the
* high and low port numbers to be used when attempting
* dispatcher connections. This global information is used
* elsewhere.
*/
#ifndef INADDR_NONE
#define INADDR_NONE -1
#endif /* INADDR_NONE */
static NI_HandPtr
DispatchConnect(NI_DispatcherPtr disp, CharPtr host, CharPtr service, int timeout)
{
struct hostent PNTR dispHost, PNTR localHost;
struct sockaddr_in serv_addr;
NI_HandPtr dHP;
Uint2 disp_port;
Uint4 srvadd;
Char servInetAddr[INETADDR_SIZ], localHostName[SVC_HOST_SIZ];
Char t_service[64];
int status;
Int4 connectStartTime;
if (disp == NULL)
return NULL;
serv_addr.sin_family = AF_INET;
srvadd = inet_addr(host);
if ((Int4)srvadd != INADDR_NONE) /* malformed request */
MemCopy((VoidPtr) &serv_addr.sin_addr, (VoidPtr) &srvadd, sizeof(srvadd));
else {
if ((dispHost = gethostbyname(host)) == NULL) {
ni_errno = NIE_NOHOSTENT;
return NULL;
}
/* MemCopy((VoidPtr)&serv_addr.sin_addr, (VoidPtr)(dispHost->h_addr), dispHost->h_length);*/
MemCopy(&serv_addr.sin_addr, dispHost->h_addr, dispHost->h_length);
}
StringCpy(servInetAddr, inet_ntoa(serv_addr.SIN_ADDR));
if ((disp_port = GetByConfigOrServ(service, TRUE)) == 0)
{
if (service)
StringCpy(t_service, service); /* because Windows barfs on the pointer */
else
t_service[0] = 0;
if ((disp_port = htons(atoi(t_service))) == 0)
disp_port = htons(NI_DFLT_SVC_PORT);
}
if (ntohs(disp_port) <= NI_LAST_RESERVED_PORT) {
ni_errno = NIE_NOSERVENT;
return NULL;
}
/* get the Internet address of the "local host" */
#ifdef NETP_INET_MACTCP
/* simpler solution to avoid the hazards of gethostname() */
{
unsigned long localHostId;
localHostId = gethostid();
StringCpy(disp->localHostAddr, inet_ntoa(* (H_ADDR_TYPE) &localHostId));
}
#else
gethostname(localHostName, SVC_HOST_SIZ);
if ((localHost = gethostbyname(localHostName)) == NULL) {
/* Nlm_Nlm_GetAppParam() workaround for PC-NFS 5.0 bug */
if (GetAppParam("NCBI", "NET_SERV", "HOST_ADDRESS", "",
disp->localHostAddr, sizeof(disp->localHostAddr)) <= 0)
{ /* use a bogus address which the dispatcher will try to fix */
StringCpy(disp->localHostAddr, "0.0.0.0");
}
} else {
StringCpy(disp->localHostAddr, inet_ntoa(* (H_ADDR_TYPE) localHost->h_addr));
}
#endif /* NETP_INET_MACTCP */
if ((disp->loport = GetByConfigOrServ(NI_CLIENT_PORT_LO_NAME, FALSE)) == 0)
{
if ((disp->loport = atoi(NI_CLIENT_PORT_LO_NAME)) == 0)
disp->loport = NI_DFLT_CLILO_PORT;
}
if (disp->loport <= NI_LAST_RESERVED_PORT) {
ni_errno = NIE_BADPORT; /* bad low client port */
return NULL;
}
if ((disp->hiport = GetByConfigOrServ(NI_CLIENT_PORT_HI_NAME, FALSE)) == 0)
{
if ((disp->hiport = atoi(NI_CLIENT_PORT_HI_NAME)) == 0)
disp->hiport = NI_DFLT_CLIHI_PORT;
}
if (disp->hiport <= NI_LAST_RESERVED_PORT) {
ni_errno = NIE_BADPORT; /* bad high client port */
return NULL;
}
MemFill((VoidPtr) &serv_addr, '\0', sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = inet_addr(servInetAddr);
serv_addr.sin_port = disp_port;
if ((dHP = MsgMakeHandle(TRUE)) == NULL)
return NULL;
MsgSetLJError(dHP);
if (timeout > 0)
MsgSetReadTimeout(dHP, timeout);
if (activityHook != NULL)
{
activityHook((NI_HandPtr) disp, NetServHook_dispconn, 0);
}
#ifdef NETP_SOCKS
if (disp->useSocks)
{ /* SOCKS can't deal well with blocking connections */
NI_SETBLOCKING(dHP->sok);
}
#endif
connectStartTime = Nlm_GetSecs();
RETRY:
#ifndef NETP_INET_NEWT
if ((status = NI_CONNECT(dHP->sok, (struct sockaddr PNTR) &serv_addr, sizeof(serv_addr))) < 0) { /* } */
#else
if ((status = NI_CONNECT(dHP->sok, &serv_addr, sizeof(serv_addr))) < 0) {
SOCK_ERRNO = ABS(status);
#endif
switch (SOCK_ERRNO) {
case EINTR:
goto RETRY;
#ifdef NETP_INET_PCNFS
/* This is apparently a bug in PC-NFS 4.0 ... a connection attempt */
/* on a non-blocking socket yields errno == 0 */
case 0:
#endif /* NETP_INET_PCNFS */
case EWOULDBLOCK:
case EINPROGRESS:
/* if the connect()ion is not established immediately, a */
/* select() can be performed where the corresponding "write" */
/* file descriptor will be enabled once the connect()ion has been*/
/* established */
status = sizeof(serv_addr);
if (sokselectw(dHP->sok, timeout) == 0
#ifdef OS_UNIX
&& getpeername(dHP->sok, &serv_addr, &status) == 0
#endif
) {
dHP->state = NI_CONNECTED;
dHP->connectDelay = Nlm_GetSecs() - connectStartTime;
return dHP;
}
break;
default:
break;
}
MsgDestroyHandle(dHP);
ni_errno = NIE_DISPCONN; /* can't connect to dispatcher */
return NULL;
}
dHP->state = NI_CONNECTED;
dHP->connectDelay = Nlm_GetSecs() - connectStartTime;
return dHP;
} /* DispatchConnect */
/*
* Purpose: Get the platform on which this client is running
*
* Parameters:
* none
*
* Returns:
* the client's platform, or NI_PLATFORM_UNKNOWN
*
*
* Description:
* Calculate what platform this client is running on.
*
*
* Note:
* Although the initial implementation of this function
* calculates the platform-type at compile-time, it is
* legitimate to perform some computation at run time, e.g.,
* to determine whether this client is using a particular
* low-level driver.
*
* The dispatcher and servers should not rely on the
* information which is received for platform-type, because
* the client may be lying, either because of a coding error
* or malice on the part of a client developer.
*/
Int2
NI_GetPlatform (void)
{
static Boolean alreadyInited = FALSE;
static Int2 retval;
if (alreadyInited)
{
return retval;
}
alreadyInited = TRUE;
retval = NI_PLATFORM_UNKNOWN;
#ifdef NETP_INET_MACTCP
retval = NI_PLATFORM_MAC;
#endif
#ifdef OS_VMS
#ifdef NETP_INET_TGV
retval = NI_PLATFORM_VMS_TGV;
#endif
#ifdef NETP_INET_TWG
retval = NI_PLATFORM_VMS_TWG;
#endif
#ifdef NETP_INET_WPW
retval = NI_PLATFORM_VMS_WPW;
#endif
#ifdef NETP_INET_UCX
retval = NI_PLATFORM_VMS_UCX;
#endif
#ifdef OS_AXP_VMS
retval = NI_PLATFORM_AXP_OPENVMS;
#endif
#endif /* OS_VMS */
#ifdef OS_UNIX
retval = NI_PLATFORM_GENERIC_UNIX;
#ifdef PROC_IBM370
retval = NI_PLATFORM_IBM370AIX;
#endif
#ifdef OS_UNIX_SUN
retval = NI_PLATFORM_SUN;
#endif
#if defined(OS_UNIX_OSF1) && defined(PROC_ALPHA)
retval = NI_PLATFORM_ALPHA_OSF1;
#endif
#ifdef COMP_AUX
retval = NI_PLATFORM_AUX;
#endif
#if defined(COMP_CRAY) && defined(PROC_YMP)
retval = NI_PLATFORM_CRAY;
#endif
#ifdef PROC_CONVEX
retval = NI_PLATFORM_CONVEX;
#endif
#ifdef PROC_HPPA
retval = NI_PLATFORM_HPUX;
#endif
#ifdef OS_UNIX_NEXT
retval = NI_PLATFORM_NEXT;
#endif
#ifdef PROC_MIPS
retval = NI_PLATFORM_SGI;
#endif
#ifdef OS_UNIX_ULTRIX
retval = NI_PLATFORM_ULTRIX;
#endif
#if defined(OS_UNIX_SYSV) && defined(PROC_SPARC)
retval = NI_PLATFORM_SYSV_ON_SPARC;
#endif
#ifdef OS_UNIX_AIX
retval = NI_PLATFORM_AIX;
#endif
#ifdef OS_UNIX_LINUX
retval = NI_PLATFORM_LINUX;
#endif
#endif /* OS_UNIX */
#ifdef OS_DOS
retval = NI_PLATFORM_DOS;
#ifdef WIN16
retval = NI_PLATFORM_WIN16;
#endif
#ifdef NETP_INET_NEWT
retval = NI_PLATFORM_WIN_NEWT;
#endif
#ifdef NETP_INET_PCNFS
retval = NI_PLATFORM_WIN_PCNFS;
#endif
#ifdef WINSOCK
retval = NI_PLATFORM_WIN_WINSOCK;
#endif
#endif /* OS_DOS */
#ifdef OS_WINNT
retval = NI_PLATFORM_WINNT;
#endif
return retval;
}
/*
* Purpose: Set the "identity" of this client
*
* Parameters:
* disp A pointer to the dispatcher structure
* user New Username
* group New Groupname
* domain New DomainName
*
* Returns:
* 0, always
*
*
* Description:
* Allocate the space for the "UID" structure, if not already
* allocated, and populate it with the user name, group name,
* and domain name.
*/
static Int2
SetIdentity(NI_DispatcherPtr disp, CharPtr user, CharPtr group, CharPtr domain)
{
if (disp == NULL)
return 0;
if (disp->identity == NULL)
disp->identity = NI_MakeUid();
if (disp->identity->username != NULL)
MemFree(disp->identity->username);
disp->identity->username = StringSave(user);
if (disp->identity->group != NULL)
MemFree(disp->identity->group);
if (group != NULL)
disp->identity->group = StringSave(group);
else
disp->identity->group = NULL;
if (disp->identity->domain != NULL)
MemFree(disp->identity->domain);
disp->identity->domain = StringSave(domain);
return 0;
} /* SetIdentity */
/*
* Purpose: Copy from the "identity" UID to the specified UID data struct
*
* Parameters:
* disp A pointer to the dispatcher structure
* uid UID structure to be copied into
*
* Returns:
* -1, if invalid arguments
* 0, otherwise
*
*
* Description:
* Copy fields from the "identity" UID data structure into the
* UID data structure provided by the caller.
*/
static Int2
CopyIdentity(NI_DispatcherPtr disp, NI_UidPtr uid)
{
if (disp == NULL || disp->identity == NULL || uid == NULL)
return -1;
if (uid->username != NULL)
MemFree(uid->username);
uid->username = StringSave(disp->identity->username);
if (uid->group != NULL)
MemFree(uid->group);
uid->group = StringSave(disp->identity->group);
if (uid->domain != NULL)
MemFree(uid->domain);
uid->domain = StringSave(disp->identity->domain);
return 0;
} /* CopyIdentity */
/*
* Purpose: Select the next available port within the given range,
* and bind a socket to it.
*
* Parameters:
* sok Socket to be bound to a port (INPUT)
* sokadr Socket data structure to be populated (OUTPUT)
* loport Minimum acceptable port number
* hiport Maximum acceptable port number
*
* Returns:
* 0, if unable to bind to a port
* the selected ("bound") port number, otherwise
*
*
* Description:
* Iterate through the range of acceptable port numbers, until
* an unused port number can be selected to which the socket
* can be bound.
*/
static Uint2
bindPort(int sok, struct sockaddr_in PNTR sokadr, Int2 loport, Int2 hiport, Uint4 remoteHost)
{
int status;
#ifdef NETP_INET_MACTCP
int delta = 0;
Char buf[20];
#endif
if (hiport == 0)
hiport = loport;
if (loport > hiport)
return 0;
#ifdef NETP_INET_MACTCP
/* use a hint from the configuration file to avoid port # conflicts */
if (hiport > loport && Nlm_GetAppParam("NCBI", "NET_SERV", "PORT_DELTA", "0",
buf, sizeof buf) > 0)
{
delta = atoi(buf);
loport += delta % (hiport - loport);
sprintf (buf, "%d", delta + 1);
SetAppParam("NCBI", "NET_SERV", "PORT_DELTA", buf);
}
#endif
MemFill((VoidPtr) sokadr, '\0', sizeof(struct sockaddr_in));
sokadr->sin_family = AF_INET;
sokadr->sin_addr.s_addr = INADDR_ANY;
while (loport <= hiport) {
sokadr->sin_port = htons(loport);
#ifdef NETP_INET_NEWT
if ((status = NI_BIND(sok, sokadr, sizeof(struct sockaddr_in), htonl(remoteHost))) == 0)
#else
if ((status = NI_BIND(sok, (struct sockaddr PNTR) sokadr, sizeof(struct sockaddr_in), htonl(remoteHost))) == 0)
#endif /* NETP_INET_NEWT */
return (Uint2) ntohs(sokadr->sin_port);
else {
#ifdef NETP_INET_NEWT
SOCK_ERRNO = ABS(status);
#endif
loport++;
}
}
return 0;
} /* bindPort */
/* SERVER FUNCTIONS */
static int writepipe PROTO((int fd, char *buf, int len));
/*
* Purpose: Write a message on the pipe from a child server application
* process to its parent NCBID.
*
* Parameters:
* fd Pipe file descriptor
* buf Buffer to be written
* len Length of buffer
*
* Returns:
* 0, if unable to write because the pipe is full
* number of bytes written, otherwise
*
*
* Description:
* Write the specified number of bytes to a pipe, and handle
* multiple write attempts if necessary, to handle the case where
* a write() may be interrupted by a signal.
*
* Note:
* This routine is only used by a child process after it has been
* forked and before it has been execed.
*/
static int
writepipe(int fd, char *buf, int len)
{
int byteswrit;
WriteAgain:
if ((byteswrit = write(fd, buf, len)) < 0) {
switch (errno) {
case EINTR:
goto WriteAgain;
case EWOULDBLOCK:
default:
return 0;
}
}
return byteswrit;
} /* writepipe */
static Int2 StandAlonePort(void)
{
CharPtr env;
#ifdef OS_UNIX
if ((env = getenv("NI_STANDALONE_SERVER")) == NULL)
{
return 0;
}
return atoi(env);
#else
return 0;
#endif
}
/*
* Purpose: Send an "ACK" from a child server application process to its
* parent NCBID.
*
* Returns:
* 0, if the ACK was sent successfully
* -1, otherwise
*
*
* Description:
* Write an "ACK" from a child server application process to its
* parent NCBID, on the pipe connecting the two processes.
*
* Note:
* This routine should be called by a child process after it has
* determined that it has started successfully. At most one
* of NI_ServerACK() and NI_ServerNACK() may be called.
*/
#define TEMP_BUF_SIZ 256
int
NI_ServerACK(void)
{
int wstat;
Char temp_buf[TEMP_BUF_SIZ];
Int2 port;
if ((port = StandAlonePort()) == 0)
{ /* not stand-alone */
sprintf(temp_buf, PIPE_MSG_FMT, NIE_SERVACK, "OK");
if ((wstat = writepipe(STDPIPE, temp_buf, strlen(temp_buf))) <= 0) {
ni_errno = NIE_PIPEIO;
strcpy(ni_errtext, (wstat == 0) ? "EWOULDBLOCK" : sys_errlist[errno]);
return -1;
}
} else { /* stand-alone */
#ifdef OS_UNIX
/* non-UNIX platforms currently experience compilation errors */
struct sockaddr_in soktAddr;
NI_HandPtr hp;
int sok;
int status;
struct sockaddr_in sockaddr;
int soktLen;
CharPtr security;
int one = 1; /* for SO_REUSEADDR */
hp = MsgMakeHandle(TRUE);
NI_SETBLOCKING(hp->sok);
MemFill(&sockaddr, '\0', sizeof(struct sockaddr_in));
sockaddr.sin_family = AF_INET;
sockaddr.sin_addr.s_addr = INADDR_ANY;
sockaddr.sin_port = htons(port);
if (setsockopt(hp->sok, SOL_SOCKET, SO_REUSEADDR, (char *) &one,
sizeof(one)) < 0)
{
Message (MSG_ERROR, "Unable to set socket re-usability, errno = %d", errno);
}
#ifdef NETP_INET_NEWT
if ((status = bind(hp->sok, &sockaddr, sizeof(struct sockaddr_in))) == 0)
#else
if ((status = bind(hp->sok, (struct sockaddr PNTR) &sockaddr, sizeof(struct sockaddr_in))) != 0)
#endif /* NETP_INET_NEWT */
{ /* error */
ErrPostEx(SEV_FATAL,0,0,
"Bind failed on socket %d, status = %d, errno = %d",
hp->sok, status, errno);
return -1;
}
NI_LISTEN(hp->sok, 1);
close(0); /* so that accept() will return 0 */
soktLen = sizeof(soktAddr);
/* accept the connection */
if ((sok = NI_ACCEPT(hp->sok, (struct sockaddr *) &soktAddr, &soktLen)) < 0)
{ /* error */
ErrPostEx(SEV_FATAL,0,0, "Accept returned bad file descriptor %d, errno = %d",
sok, errno);
return -1;
}
LOG_SOCKET(sok, TRUE);
MsgDestroyHandle(hp);
if ((security = getenv("NI_STANDALONE_SECURITY")) != NULL)
{ /* security must be substring of client address */
if (StrNCmp(inet_ntoa(soktAddr.SIN_ADDR), security, StrLen(security)) != 0)
{
close(sok);
ErrPostEx(SEV_FATAL,0,0, "Security violation from IP address %s, security = %s\n",
inet_ntoa(soktAddr.SIN_ADDR), security);
return -1;
}
}
#endif /* OS_UNIX */
}
return 0;
} /* NI_ServerACK */
/*
* Purpose: Send an "NACK" from a child server application process to its
* parent NCBID.
*
* Returns:
* 0, if the NACK was sent successfully
* -1, otherwise
*
*
* Description:
* Write an "NACK" from a child server application process to its
* parent NCBID, on the pipe connecting the two processes.
*
* Note:
* This routine should be called by a child process after it has
* determined that it will be unable to start successfully. In
* the event that this routine is not called (or is unable to
* perform its function), a timeout mechanism must be relied
* upon for the NCBID to realize that a child has started
* unsuccessfully.
*
* At most one of NI_ServerACK() and NI_ServerNACK() may be called.
*/
int
NI_ServerNACK(CharPtr err_text)
{
int wstat;
Char temp_buf[TEMP_BUF_SIZ];
sprintf(temp_buf, PIPE_MSG_FMT, NIE_SERVNACK, err_text);
if (StandAlonePort() == 0)
{ /* not stand-alone */
if ((wstat = writepipe(STDPIPE, temp_buf, strlen(temp_buf))) <= 0) {
ni_errno = NIE_PIPEIO;
strcpy(ni_errtext, (wstat == 0) ? "EWOULDBLOCK" : sys_errlist[errno]);
return -1;
}
} else { /* stand-alone */
ErrPostEx(SEV_FATAL,0,0, "Stand-alone server failed startup {%s}", temp_buf);
return -1;
}
return 0;
} /* NI_ServerNACK */
/*
* Purpose: Open the stream to be used for ASN I/O between a server
* application process and its client.
*
* Returns:
* NULL, if something went wrong
* a pointer to the Msg structure, otherwise
*
*
* Description:
* Create a "Msg" structure for ASN I/O, and associate the Msg's
* socket with the standard input file descriptor (STDIN), which is
* the communication socket between the server application process
* and its client.
*
* Note:
* This routine should only be called by a child application
* process (not by a client).
*/
NI_HandPtr
NI_OpenASNIO(void)
{
NI_HandPtr hp;
if ((hp = MsgMakeHandle(FALSE)) == NULL)
return NULL;
MsgSetReadTimeout(hp, NI_SERV_LISTEN_TIMEOUT); /* set default for servers to listen */
if ((hp->sok = dup(STDIN)) == -1) { /* STDOUT points to same socket */
MsgDestroyHandle(hp);
return NULL;
}
LOG_SOCKET(hp->sok, TRUE);
{
CharPtr buf;
Char key[8];
if ((buf = getenv("NI_DESKEY")) != NULL &&
AsnTypeStringToHex(buf, StrLen(buf), key))
{
NI_SetupDESEncryption(hp, (UcharPtr) key);
}
}
return hp;
} /* NI_OpenASNIO */
/*
* Purpose: Close the ASN stream between a server application process and
* its client.
*
* Returns:
* -1 if something went wrong
* 0, otherwise
*
*
* Description:
* Close the stream by closing the socket and deleting the
* associated data structures.
*
* Note:
* This routine should only be called by a child application
* process (not by a client).
*/
Int2
NI_CloseASNIO(NI_HandPtr hp)
{
return MsgDestroyHandle(hp);
} /* NI_CloseANSIO */
/* MISC FUNCTIONS */
/* sokselectr and sokselectw are not prototyped in ni_lib.h */
/*
* Purpose: Wait for a "read" socket to become ready to read, or for
* a timeout to occur.
*
* Returns:
* -1 if something went wrong
* 0, otherwise
*
*
* Description:
* Wait for the indicated "read" socket to be marked as
* "selected" by a socket() call.
*
* Note:
* This routine is presently unused.
*
* The timeout mechanism is not exactly enforced, because
* received signals could result in a longer timeout period.
*/
int
sokselectr(int fd)
{
fd_set rfds;
int ready;
struct timeval timeout;
FD_ZERO(&rfds);
FD_SET(fd, &rfds);
timeout.tv_sec = NI_SELECT_TIMEOUT;
timeout.tv_usec = 0;
while ((ready = select(fd+1, &rfds, NULL, NULL, &timeout)) == -1) {
switch (SOCK_ERRNO) {
case EINTR:
continue;
default:
ni_errno = NIE_MISC;
sprintf(ni_errtext, "%s", sys_errlist[SOCK_INDEX_ERRNO]);
return -1;
}
}
if (ready == 0) {
strcpy(ni_errtext, ni_errlist[ni_errno]);
ni_errno = NIE_TIMEOUT;
return -1;
}
if (FD_ISSET(fd, &rfds))
return 0;
else
return -1;
} /* sokselectr */
/*
* Purpose: Wait for a "write" socket to become ready to write, or for
* a timeout to occur.
*
* Returns:
* -1 if something went wrong
* 0, otherwise
*
*
* Description:
* Wait for the indicated "write" socket to be marked as
* "selected" by a socket() call.
*
* Note:
* This routine can be used when waiting for a connect() to go
* through successfully.
*
* The timeout mechanism is not exactly enforced, because
* received signals could result in a longer timeout period.
*/
int
sokselectw(int fd, int seconds)
{
fd_set wfds;
int ready;
struct timeval timeout;
FD_ZERO(&wfds);
FD_SET(fd, &wfds);
timeout.tv_sec = NI_SELECT_TIMEOUT;
if (seconds > 0) /* override default */
timeout.tv_sec = seconds;
timeout.tv_usec = 0;
while ((ready = select(fd+1, NULL, &wfds, NULL, &timeout)) == -1) {
switch (SOCK_ERRNO) {
case EINTR:
continue;
default:
ni_errno = NIE_MISC;
sprintf(ni_errtext, "%s", sys_errlist[SOCK_INDEX_ERRNO]);
return -1;
}
}
if (ready == 0) {
strcpy(ni_errtext, ni_errlist[ni_errno]);
ni_errno = NIE_TIMEOUT;
return -1;
}
if (FD_ISSET(fd, &wfds))
{
#ifdef OS_UNIX
int err;
int optlen;
optlen = sizeof(int);
if (getsockopt(fd, SOL_SOCKET, SO_ERROR, (char *) &err, &optlen) >= 0 &&
err != 0) /* check for an error */
return -1; /* got some error */
#endif /* OS_UNIX */
return 0;
}
else
return -1;
} /* sokselectw */
/*
* Purpose: Parse the error number from an ASN error string which was
* formatted at a low level.
*
* Returns:
* -1, if unable to parse the string
* the parsed error number, otherwise
*
*
* Description:
* Parse the error number from an ASN error string which was
* prepared by the ASN tools, and formatted at a low level.
*
* Note:
* The parsing mechanism is dependent upon any future format
* changes which may occur in the ASN tools.
*/
int
getAsnError(char *str)
{
int errnum;
if (sscanf(str, "%*s %*s %*s [-%d]", &errnum) < 1)
errnum = -1;
return errnum;
} /* getAsnError */
/*
* Purpose: Set the Connection ID file pointer
*
* Parameters:
* fp The new value for the file descriptor
*
*
* Description:
* Set the Connection ID file pointer. This is used to update
* the connection ID each time it is updated, to keep the
* value current.
*
* Note:
* In reality, this should only be called by the dispatcher.
*/
void
SetConFilePtr (FILE *fp)
{
conid_fp = fp;
}
/*
* Purpose: Update the connection ID file
*
* Parameters:
* conid The new connection ID value
*
*
* Description:
* Update the connection ID file, and be sure to flush the stream,
* to try to ensure that output really occurs.
*
* Note:
* Should be called every time the "next" connection ID is
* modified.
*/
void
WriteConFile (Uint4 conid)
{
if (conid_fp != NULL) {
(void) fseek(conid_fp, 0L, SEEK_SET);
(void) FileWrite((CharPtr) &conid, 1, sizeof(conid), conid_fp);
(void) fflush (conid_fp);
}
}
/*
* Purpose: Close the connection ID file
*
*
* Description:
* Close the connection ID file.
* to try to ensure that output really occurs.
*
* Note:
* In reality, this should only be called by the dispatcher.
*/
void
CloseConFile (void)
{
if (conid_fp != NULL) {
fclose (conid_fp);
conid_fp = NULL;
}
}
/*
* Purpose: Check for expired timers
*
*
* Description:
* For every expired timer, call the specified timer
* callback function, which is in turn responsible for cancelling
* the timer.
*
* Note:
* Timer checks only take place when this function is called.
* Therefore, it is the responsibility of an application to
* intermitently call this function. This could be done, e.g.
* using the UNIX alarm clock mechanism, or inside of an event
* loop.
*
* The order of operations is significant here, because the
* hook function must cancel the timer. To perform the linked
* list traversal in a less careful manner could result in
* illegal memory accesses.
*
* The timer list in managed in a very unsophisticated manner;
* if lots of timers were anticipated, the list would be
* maintained sorted by time, and all of the timer functions
* would need to maintain and traverse the timer list based
* upon this criterion.
*
* A count is used as a failsafe mechanism against infinite loops.
*/
#define NI_MAX_TIMERS 1000
void
NI_ProcessTimers(void)
{
NodePtr t;
NodePtr tnew;
NI_TimerPtr timer;
time_t curtime;
int count = NI_MAX_TIMERS;
if ((t = timerHead) == NULL)
{
return;
}
curtime = GetSecs();
do {
timer = (NI_TimerPtr) t->elem;
tnew = ListGetNext(t);
if (timer != NULL && timer->timeout != NULLB &&
timer->timeout <= curtime)
{ /* fire timer */
/* mark the timer so it won't fire again */
timer->timeout = NULLB;
if (timer->hook != NULL)
{
timer->hook(timer->hookParam);
}
}
if (t == tnew)
{ /* data structure error, time to bail out */
break;
}
t = tnew;
} while (t != timerHead && t != NULL && --count > 0);
}
/*
* Purpose: Return the time when the next timeout will occur
*
* Returns: The time, in seconds, when the next scheduled timeout will
* occur, or NULLB, if there are no timers set.
*
* Description:
* Return the time when the next timer timeout will occur.
* This information is typically used with the select()
* system call, to ensure that a timeout parameter is passed
* to select() which is sufficiently short to ensure that
* the application will call NI_ProcessTimers() at an
* appropriate time.
*
* Note:
* The timer list in managed in a very unsophisticated manner;
* if lots of timers were anticipated, the list would be
* maintained sorted by time, and all of the timer functions
* would need to maintain and traverse the timer list based
* upon this criterion.
*/
time_t
NI_GetNextWakeup(void)
{
time_t next_wakeup = NULLB;
NodePtr t;
NI_TimerPtr timer;
NI_ProcessTimers();
if ((t = timerHead) == NULL)
{
return NULLB;
}
do {
t = ListGetNext(t);
timer = (NI_TimerPtr) t->elem;
if (next_wakeup == NULLB || (timer->timeout != NULLB &&
timer->timeout < next_wakeup))
{
next_wakeup = timer->timeout;
}
} while (t != timerHead && t != NULL);
return next_wakeup;
}
/*
* Purpose: Set a timer
*
* Parameters:
* timeout The time in seconds when the timer should expire
* hook Callback to be called when (if) the timer expires
* hookParam Parameter to be passed to caller's hook when the timer expires
*
*
* Returns: The "timer ID", really a pointer to the timer data structure
*
*
* Description:
* Sets a timer with the appropriate parameters.
*
* Note:
* The timer list in managed in a very unsophisticated manner;
* if lots of timers were anticipated, the list would be
* maintained sorted by time, and all of the timer functions
* would need to maintain and traverse the timer list based
* upon this criterion.
*
* It is the responsibility of the application (usually the
* hook function) to cancel the timer.
*/
NodePtr
NI_SetTimer(time_t timeout, NI_TimeoutHook hook, Pointer hookParam)
{
NodePtr t;
NI_TimerPtr timer;
timer = (NI_TimerPtr) MemNew(sizeof(NI_Timer));
timer->timeout = timeout;
timer->hook = hook;
timer->hookParam = hookParam;
t = ListInsert(timer, timerHead);
timerHead = t;
return t;
}
/*
* Purpose: Cancel a timer
*
* Parameters:
* timerID The ID of the timer
*
*
* Description:
* Cancel the specified timer by deleting the entry and its
* associated data structure.
*
* Note:
* The timer list in managed in a very unsophisticated manner;
* if lots of timers were anticipated, the list would be
* maintained sorted by time, and all of the timer functions
* would need to maintain and traverse the timer list based
* upon this criterion.
*/
void
NI_CancelTimer(NodePtr timerId)
{
if (timerId != NULL)
{
MemFree (timerId->elem);
timerHead = ListDelete(timerId);
}
NI_ProcessTimers();
}
/*
* Purpose: Set an activity hook, to inform the application of key events
*
* Parameters:
* hook The hook (callback function)
*
*
* Description:
* Setup a hook function which will subsequently be used to
* inform the application of various events; these currently
* include:
* * Connection to dispatcher
* * Disconnection from dispatcher
* * Service connection
* * Service disconnection
* * Bytes written
* * Bytes read
*
* Note:
* This hook is global for the running application.
*/
void
NI_SetActivityHook (NI_NetServHook hook)
{
activityHook = hook;
}
/*
* Purpose: Return the current activity hook
*
*
* Description:
* Return the current activity hook. This is only intended
* to be used internally by the Network Services library.
* This function is used to avoid making activityHook into a
* global variable.
*/
NI_NetServHook
NI_ActivityHook (void)
{
return activityHook;
}
/*
* Purpose: Initialize socket management
*
* Description:
* If not already initialized, initialize the socket management
* data structures
*/
static void
InitLogSocket()
{
static Boolean inited = FALSE;
if (! inited)
{
FD_ZERO(&openfds);
inited = TRUE;
}
}
/*
* Purpose: Count the number of open sockets
*/
Int2
NI_SocketsOpen(void)
{
int sok;
int count = 0;
InitLogSocket();
for (sok = 0; sok < FD_SETSIZE; sok++)
{
if (FD_ISSET(sok, &openfds))
count++;
}
return count;
}
/*
* Purpose: Log each socket transaction
*/
void
NI_LogSocket(int sok, Boolean opening, CharPtr filename, int lineno)
{
int localsok;
InitLogSocket();
if (sok == INVALID_SOCKET || sok < 0 || sok >= FD_SETSIZE)
{
#ifndef NETP_INET_WSOCK
/* FD_SETSIZE doesn't accurately describe the socket range for
WinSock applications, so don't generate misleading error msgs */
ErrPostEx(SEV_WARNING,0,0, "Bad %s operation on socket %d at %s:%d",
opening ? "opening" : "closing", sok, filename, lineno);
#endif /* NETP_INET_WSOCK */
return;
}
if (opening)
{
TRACE("Just opened socket %d at %s:%d\n", sok, filename, lineno);
if (FD_ISSET(sok, &openfds))
{
ErrPostEx(SEV_ERROR,0,0, "Duplicate open of socket %d at %s:%d",
sok, filename, lineno);
} else {
FD_SET(sok, &openfds);
}
} else {
TRACE("Trying to close socket %d at %s:%d\n", sok, filename, lineno);
if (FD_ISSET(sok, &openfds))
{
FD_CLR(sok, &openfds);
} else {
ErrPostEx(SEV_ERROR,0,0, "Duplicate close of socket %d at %s:%d",
sok, filename, lineno);
}
}
#ifdef DEBUG
for (localsok = 0; localsok < FD_SETSIZE; localsok++)
{
if (FD_ISSET(localsok, &openfds))
{
TRACE("Socket %d is currently open\n", localsok);
}
}
#endif /* DEBUG */
}